Eventos

Webhooks

Te avisamos cada cambio de un pago con un POST firmado. Si tu servidor falla, reintentamos durante unas 9 horas.

Registrar un webhook

terminal
npx talo-hub webhooks add https://tu-sitio.com/api/webhooks/talo
# → whsec_…  (guardalo en TALO_HUB_WEBHOOK_SECRET)

El secreto se muestra una sola vez. También podés registrarlo por API (POST /api/v1/webhook-endpoints) o desde el dashboard.

Eventos

payment.createdSe creó un pago.
payment.paidEl pago quedó SUCCESS u OVERPAID: entregá el pedido.
payment.updatedCambió el estado, el monto pagado o un reembolso.
payment.expiredEl pago venció sin pagarse.
account.updatedTalo cambió el estado de la cuenta (por ejemplo, la activó).
webhook.testEvento de prueba enviado con /test: confirmalo con 2xx y no hagas nada más.
body
{
  "id": 1842,
  "type": "payment.paid",
  "created_at": "2026-10-28T15:04:31.000Z",
  "data": {
    "payment": { "id": "VAR-…", "status": "SUCCESS", "external_id": "pedido-1001",
                 "amount": 15000, "paid": 15000, "cvu": "…", "alias": "…" },
    "previous_status": "PENDING"
  }
}

Verificar la firma

Cada pedido trae Talo-Hub-Signature: t=<unix>,v1=<hex>, donde v1 es el HMAC-SHA256 de <t>.<body crudo> con tu secreto. Verificalo sobre el body sin parsear y rechazá firmas de más de 5 minutos.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyTaloHub(rawBody: string, header: string, secret: string) {
  const parts = Object.fromEntries(header.split(",").map((p) => {
    const [k, ...v] = p.trim().split("=");
    return [k, v.join("=")];
  }));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  if (!/^[0-9a-f]{64}$/.test(parts.v1 ?? "")) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return timingSafeEqual(expected, Buffer.from(parts.v1, "hex"));
}

Buenas prácticas

  • Respondé 2xx rápido y procesá después.
  • Procesá cada evento una sola vez, por su id: los reintentos pueden repetirlo.
  • Entregá el pedido con payment.paid. UNDERPAID significa que transfirieron de menos.
  • En desarrollo, usá npx talo-hub events listen --forward-to http://localhost:3000/… en vez de exponer tu máquina.