04 — Webhooks
Los webhooks son la forma recomendada de enterarte de un cobro en cuanto
ocurre, sin hacer polling a GET /v1/transactions. Requiere webhooks:read
para consultar y webhooks:write para crear, modificar, probar o rotar.
POST /v1/webhooks
curl -X POST https://api.yallego.app/v1/webhooks \
-H "Authorization: Bearer yk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-sistema.pe/hooks/yallego",
"subscribed_events": ["transaction.created", "device.offline"],
"description": "Integración con sistema de ventas"
}'
{
"id": "...",
"url": "https://mi-sistema.pe/hooks/yallego",
"secret": "whsec_3f6a8c2e1b5d7f9a3c6e9f8c2a1e4b7d",
"subscribed_events": ["transaction.created", "device.offline"],
"is_enabled": true,
"created_at": "2026-05-14T18:50:00Z"
}
Requisitos de la URL:
- Debe ser
https://— se rechazahttp://con400 VALIDATION_ERRORy el campourlendetails.fields(ver errores de validación). - Puede usar cualquier puerto (
https://mi-sistema.pe:8443/hooks): no hay una lista de puertos permitidos. Lo que se valida son las direcciones. - No puede resolver a una red privada, de enlace local, o loopback (SSRF): todas las direcciones del dominio tienen que ser públicas. La validación ocurre al crear el endpoint y antes de cada intento de entrega. Si después de creado tu dominio empieza a resolver a una IP privada, la entrega se abandona en ese intento, sin reintentos; si deja de resolver (un corte de DNS), ese intento cuenta como fallido y la entrega sigue el calendario de reintentos (ver 08 — Política de reintentos).
- Las entregas no siguen redirecciones: un
3xxcuenta como fallo. Registra la URL final. - El número de endpoints está limitado por plan (
webhooksen los límites del plan); superarlo responde422 PLAN_LIMIT_EXCEEDED. Es un cupo aparte del de claves de API: crear claves no consume webhooks.
secret se muestra una única vez en esta respuesta. Guárdalo — lo
necesitas para verificar la firma de cada
evento entrante.
subscribed_events acepta los eventos del catálogo. Un
evento fuera del catálogo responde 400 VALIDATION_ERROR; eso incluye
notification.unmatched, que se retiró porque nunca se emitió (ver «Eventos
retirados» en el catálogo de eventos).
Cada evento trae en el sobre tenant_id (el comercio) y livemode, así que
puedes registrar la misma URL en varios comercios y enrutar por
tenant_id (ver «Un endpoint para varios comercios» en el
catálogo de eventos).
Envía Idempotency-Key al registrar: si la respuesta se pierde y repites con
la misma clave, recibes el mismo endpoint (y el mismo secret) en vez de crear
un duplicado que consuma cupo del plan. La prueba, la rotación del secreto y el
reintento manual también la aceptan. Ver 10 — Idempotencia.
Gestión del endpoint
| Método | Ruta | Descripción |
|---|---|---|
GET | /v1/webhooks | Lista todos los endpoints del tenant |
GET | /v1/webhooks/{id} | Detalle de un endpoint |
PATCH | /v1/webhooks/{id} | Actualiza subscribed_events, description o is_enabled |
DELETE | /v1/webhooks/{id} | Elimina el endpoint permanentemente |
POST | /v1/webhooks/{id}/test | Envía un evento transaction.created de prueba con datos ficticios |
El evento de prueba viaja con el mismo sobre y la misma firma que uno real,
con el tenant_id y el livemode del comercio; solo data es ficticio. Cada
llamada crea un evento nuevo (otro id) y una entrega nueva, y responde
{ "delivery_id": "..." } para seguirla en el historial.
PATCH con subscribed_events reemplaza la lista completa. Si la lista
incluye notification.unmatched, se descarta en vez de responder 400.
GET /v1/webhooks y GET /v1/webhooks/{id} incluyen además un resumen de
las entregas creadas en las últimas 7 × 24 horas:
{
"id": "...",
"url": "https://mi-sistema.pe/hooks/yallego",
"is_enabled": true,
"consecutive_failures": 0,
"deliveries_last_7_days": { "total": 128, "delivered": 125, "failed": 1 }
}
delivered cuenta las entregas DELIVERED y failed, las ABANDONED (ver
reintentos). El resto de total está esperando un
intento (PENDING). Las respuestas de crear, actualizar y rotar no lo incluyen.
# Deshabilitar temporalmente sin borrar la configuración
curl -X PATCH https://api.yallego.app/v1/webhooks/{id} \
-H "Authorization: Bearer yk_live_..." \
-H "Content-Type: application/json" \
-d '{"is_enabled": false}'
POST /v1/webhooks/{id}/rotate-secret
Genera un secreto nuevo sin cambiar el id ni la URL del endpoint. Durante
las 24 horas siguientes, cada entrega se firma con AMBOS secretos:
X-Yallego-Signature: sha256=<firmado con el secreto NUEVO>
X-Yallego-Signature-Previous: sha256=<firmado con el secreto ANTERIOR>
Esto te da una ventana para actualizar tu verificación en producción sin
perder entregas en el camino — verifica contra X-Yallego-Signature
primero y, si no tienes el secreto nuevo desplegado aún, cae a
X-Yallego-Signature-Previous. Pasadas las 24 horas, solo se envía la
firma nueva. Esta cabecera adicional es una extensión propia de Yallegó
(no forma parte del contrato base de firma, ver guía 07).
curl -X POST https://api.yallego.app/v1/webhooks/{id}/rotate-secret \
-H "Authorization: Bearer yk_live_..."
# Respuesta: mismo shape que POST /v1/webhooks, con el nuevo `secret`
Historial de entregas
curl "https://api.yallego.app/v1/webhooks/{id}/deliveries?status=ABANDONED&limit=20" \
-H "Authorization: Bearer yk_live_..."
{
"data": [
{
"id": "...",
"event_id": "3f6a8c2e-...",
"event_type": "transaction.created",
"status": "ABANDONED",
"attempts": 8,
"max_attempts": 8,
"next_attempt_at": null,
"last_attempt_at": "2026-05-14T19:32:07Z",
"last_status_code": 503,
"last_error": "HTTP 503",
"delivered_at": null,
"created_at": "2026-05-14T18:32:09Z"
}
],
"pagination": { "has_more": true, "next_cursor": "MjAyNi0wNS0xNFQx...", "limit": 20 }
}
Las entregas vienen de la más reciente a la más antigua. Para la página
siguiente, repite la consulta con cursor=<next_cursor> y los mismos filtros,
igual que en el listado de transacciones; cuando has_more es false,
next_cursor es null. limit va de 1 a 100 (20 por omisión). Un cursor
que no salió de esta ruta responde 400 VALIDATION_ERROR.
id es el mismo valor que llega en la cabecera X-Yallego-Delivery-Id de
cada intento de esa entrega.
Una entrega pasa por tres estados:
| Estado | Significado |
|---|---|
PENDING | Esperando su intento: el primero o el siguiente del calendario. attempts, last_status_code y last_error dicen cómo fue el anterior y next_attempt_at, cuándo es el próximo |
DELIVERED | Tu endpoint respondió 2xx |
ABANDONED | No se reintenta más: agotó los 8 intentos, tu endpoint respondió 410, el dominio resolvió a una red interna, o el endpoint estaba deshabilitado (ver 08) |
IN_PROGRESS y FAILED existen en el enum (el filtro status los acepta) pero
hoy no se asignan: un intento fallido con reintentos pendientes vuelve a
PENDING, y filtrar por ellos devuelve una lista vacía. Para ver las entregas
que fallaron definitivamente, filtra por ABANDONED.
POST /v1/webhooks/{id}/deliveries/{deliveryId}/retry
Reintenta manualmente una entrega ABANDONED (o adelanta una PENDING), por
ejemplo después de arreglar un bug en tu endpoint. Hace un intento inmediato de
la misma entrega: mismo id, mismo X-Yallego-Delivery-Id y mismo cuerpo.
En una PENDING, ese intento reemplaza al que estaba agendado: no se suma uno
más.
curl -X POST https://api.yallego.app/v1/webhooks/{id}/deliveries/{deliveryId}/retry \
-H "Authorization: Bearer yk_live_..."
Responde { "delivery_id": "..." }. Reintentar una DELIVERED responde
409 CONFLICT. Si el endpoint está deshabilitado, reactívalo antes
(PATCH con {"is_enabled": true}): con el endpoint deshabilitado la entrega
se abandona sin enviarse. Qué pasa si el intento falla está en
08 — Política de reintentos.
En todas las rutas de webhooks, un {id} o {deliveryId} que no es un UUID
responde 404 NOT_FOUND, igual que uno que no existe.