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.created | Se creó un pago. |
payment.paid | El pago quedó SUCCESS u OVERPAID: entregá el pedido. |
payment.updated | Cambió el estado, el monto pagado o un reembolso. |
payment.expired | El pago venció sin pagarse. |
account.updated | Talo cambió el estado de la cuenta (por ejemplo, la activó). |
webhook.test | Evento 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.UNDERPAIDsignifica que transfirieron de menos. - En desarrollo, usá
npx talo-hub events listen --forward-to http://localhost:3000/…en vez de exponer tu máquina.