Ir al contenido

Webhooks y eventos

Cada operación que llega a un estado final emite un evento: payin.confirmed, payin.failed, payout.confirmed o payout.failed (y test.ping es el aviso de prueba, abajo). El evento lleva la operación entera en data. Se lo mandamos a su url por webhook, y además puede consultarlo con GET /v2/events.

POST /v2/webhook-endpoints con {"url": "https://…"} devuelve el secreto una sola vez: guárdelo. Una url activa por empresa: registrar otra la reemplaza, y el secreto anterior sigue valiendo 24 horas para que cambie su copia sin perder ningún aviso.

Cada aviso llega con estas cabeceras:

Cabecera Qué trae
X-Firma la firma, v1=<hex>; durante una rotación van dos separadas por espacio
X-Timestamp segundos Unix de cuándo se firmó; se rechaza fuera de la ventana
X-Evento el type del evento
X-Id-Evento el id del evento, para deduplicar

Algoritmo: HMAC-SHA256 sobre <X-Timestamp> + "." + <cuerpo crudo>, en hex; tolerancia de reloj: 300 segundos.

La firma es HMAC-SHA256 sobre X-Timestamp + "." + cuerpo crudo, en hexadecimal, con su secreto como llave, con el prefijo v1=. Para verificarla:

  1. Lea el cuerpo crudo, antes de parsear el JSON: reserializar cambia los bytes y la firma deja de coincidir.
  2. Rechace si X-Timestamp está a más de 5 minutos de su reloj: sin esto, un aviso legítimo capturado una vez sirve para siempre.
  3. Calcule el HMAC y compárelo en tiempo constante con cada firma de X-Firma (durante una rotación de secreto van dos, separadas por espacio; alcanza con que una coincida).
  4. Sólo entonces parsee el JSON y procese el evento. Deduplique por id.

Los tres SDK lo hacen por usted (ParseEvent, parseEvent, parse_event). Es la parte del SDK que más vale la pena usar: verificar mal una firma es el error que no se nota.

Desde el dashboard (Webhooks → Enviar aviso de prueba) le mandamos un aviso test.ping firmado igual que los de verdad, con las mismas cabeceras y el secreto vigente. Sirve para probar que su url contesta y que su código verifica la firma antes de que llegue el primer pago.

Su data es una operación de ejemplo: el id en ceros, status pending, amount 0.00 y metadata.test = true. No corresponde a ningún movimiento. Decida siempre por type: conteste 2xx a test.ping y no lo procese. No se reintenta ni aparece en GET /v2/events.

Conteste 2xx en menos de 10 segundos. Si no, reintentamos con espera creciente durante unas 24 horas (30 s, 2, 10, 30 min, 1, 2, 4, 8 y 8 h) y recién entonces el aviso se da por agotado; un aviso agotado se reenvía a pedido. Un mismo evento puede llegarle más de una vez: por eso el id.

GET /v2/events?cursor=&limit= devuelve sus eventos en orden con un cursor: sirve para reconciliar, para recuperar lo que no recibió y para integrar sin webhook. Guarde next_cursor.

GET/v2/events

Alcanceoperaciones:leer

El mismo evento que viaja por webhook, en orden, con cursor. Sirve para reponerse de un webhook perdido: guarde el último next_cursor y pida desde ahí. Requiere el alcance operaciones:leer.

Parámetro En Obligatorio Descripción
cursor query no El next_cursor de la respuesta anterior. Sin él, desde el principio.
limit query no
Código Significa
200 Los eventos.
400 Un parámetro inválido. Códigos: field_invalid.
401 La llave falta o no es válida. Códigos: unauthorized.
403 La llave es válida pero no tiene el alcance. Códigos: scope_missing.
GET/v2/transactions/{id}

Alcanceoperaciones:leer

Un cobro o un pago por su id, con la misma forma. Requiere el alcance operaciones:leer.

Parámetro En Obligatorio Descripción
id path sí El id de la operación.
Código Significa
200 La operación.
401 La llave falta o no es válida. Códigos: unauthorized.
403 La llave es válida pero no tiene el alcance. Códigos: scope_missing.
404 No hay una operación con ese identificador en su empresa. Códigos: not_found.
POST/v2/webhook-endpoints

Alcancewebhooks:configurar

Registra la url a la que se le mandan los eventos y devuelve el secreto de firma una sola vez. Una url activa por empresa: registrar otra la reemplaza, y el secreto anterior sigue valiendo 24 horas para que el cambio no pierda avisos.

Requiere el alcance webhooks:configurar. La url tiene que ser https.

POST a su url con el cuerpo de Evento y estas cabeceras: X-Firma (HMAC-SHA256 de <X-Timestamp>.<cuerpo> con su secreto, como v1=<hex>; durante una rotación van dos, separadas por espacio), X-Timestamp (segundos Unix), X-Evento (el type) y X-Id-Evento (el id). Conteste 2xx en menos de 10 segundos; si no, se reintenta con espera creciente durante unas 24 horas (30 s, 2, 10, 30 min, 1, 2, 4, 8 y 8 h, con un ±10 % al azar) y recién entonces se agota; un aviso agotado se reenvía a mano. Deduplique por id.

Mientras no registre nada acá, los eventos van a la url y con el secreto de la versión 1, si los tiene.

Código Significa
201 Registrada. Guarde secret: no se vuelve a mostrar.
400 Falta la url o no es https. Códigos: body_invalid, field_invalid, field_required.
401 La llave falta o no es válida. Códigos: unauthorized.
403 La llave es válida pero no tiene el alcance. Códigos: scope_missing.
Campo Tipo Obligatorio Descripción
data Operacion sí
id string sí
occurred_at string sí
type TipoDeEvento sí
POST /v2/webhook-endpoints
curl -X POST https://api.tucapi.app/v2/webhook-endpoints \
-H "Authorization: Bearer $LLAVE" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.suempresa.com/avisos"
}'