Webhooks, callbacks y entregas
Mensajería remota del ciclo de Payment Order
Diseño objetivo del módulo. Su propósito es permitir que un sistema externo siga remotamente una Payment Order desde su creación hasta su resultado terminal. El lifecycle canónico pertenece a la Payment Order; el Checkout Web es el canal o sesión que la envuelve.
Estado de implementación: payment_order.created, payment_order.presented y payment_order.terminal están implementados y certificados como callbacks productivos del Web Checkout QR.
Contrato productivo actual
Para Web Checkout QR, los callbacks externos productivos y certificados son:
payment_order.created
payment_order.presented
payment_order.terminal
payment_order.terminal usa únicamente result = pagado | cancelado | timeout. failed no es un cuarto resultado comercial terminal público.
Las entregas se firman con HMAC-SHA256 usando X-YUPY-Certification-Timestamp y X-YUPY-Certification-Signature. El receptor debe verificar el cuerpo crudo, deduplicar por event_id y responder HTTP 2xx rápidamente.
YUPY realiza hasta cinco intentos de entrega: inmediato, +30 s, +120 s, +600 s y +1800 s. Timeout, errores de conexión, HTTP 408, 425, 429 y 5xx son reintentables.
Importante: las rutas de administración de receptores o deliveries que en esta página estén marcadas como propuestas no deben interpretarse como endpoints productivos públicos hasta que su propia documentación figure como implementada y verificada.
1. payment_order.created
created— creada correctamente.rejected— rechazada por validación o regla de negocio.duplicate— operación equivalente ya existente por idempotencia.- Si la creación no puede completarse, la API devuelve un error HTTP y no se emite un resultado comercial terminal
failed.
{
"event": "payment_order.created",
"result": "created",
"message": "Payment Order created successfully.",
"external_transaction_id": "TX-12345",
"payment_order_uid": "po_...",
"checkout_uid": "chk_...",
"amount": "125.50",
"currency": "PEN",
"payment_method": "qr",
"channel": "web",
"occurred_at": "2026-08-28T03:39:50Z"
}
2. payment_order.presented
presented— presentado correctamente.presentation_failed— no pudo presentarse.expired_before_presentation— expiró antes de presentarse.cancelled_before_presentation— fue cancelada antes de presentarse.
{
"event": "payment_order.presented",
"result": "presented",
"message": "Payment method presented to customer.",
"external_transaction_id": "TX-12345",
"payment_order_uid": "po_...",
"payment_method": "qr",
"channel": "web",
"presented_at": "2026-08-28T03:40:02Z",
"occurred_at": "2026-08-28T03:40:02Z"
}
3. payment_order.terminal
pagado— pago confirmado.cancelado— operación cancelada.timeout— venció sin pago confirmado.- No existe
failedcomo cuarto resultado comercial terminal público. rejected— reservado para medios futuros que expongan rechazo terminal explícito.
{
"event": "payment_order.terminal",
"result": "pagado",
"message": "Payment confirmed.",
"external_transaction_id": "TX-12345",
"payment_order_uid": "po_...",
"amount": "125.50",
"currency": "PEN",
"payment_method": "qr",
"channel": "web",
"presented_at": "2026-08-28T03:40:02Z",
"paid_at": "2026-08-28T03:41:18Z",
"occurred_at": "2026-08-28T03:41:18Z"
}
Datos comunes de correlación
Los mensajes deben incluir, cuando corresponda, external_transaction_id, payment_order_uid, checkout_uid, amount, currency, payment_method, channel y occurred_at. Según el evento también pueden incluir checkout_created_at, presented_at, paid_at, timeout_seconds, result y message.
Flujo conceptual: payment_order.created → payment_order.presented → payment_order.terminal.
Webhooks, callbacks y entregas
Estado documental: Contrato propuesto. Las rutas, campos, límites y tiempos pasan a estado confirmado solo después de su implementación y verificación.
Objetivo: Documentar el registro de callbacks, sus entregas, firma, reintentos y eventos cliente dentro del contrato público Payment Order / API Engine.
Endpoints receptores
La empresa registra previamente uno o varios endpoints HTTPS. Las Payment Orders no aceptan URLs arbitrarias sin autorización.
| Audiencia | Método | Ruta propuesta | Uso | Idempotencia | Eventos o callbacks relacionados |
|---|---|---|---|---|---|
| Integración | POST |
/v1/webhook-endpoints |
Registrar receptor. | Obligatoria | webhook.endpoint_created |
| Integración | GET |
/v1/webhook-endpoints |
Listar receptores. | No | Ninguno |
| Integración | GET |
/v1/webhook-endpoints/{id} |
Consultar receptor. | No | Ninguno |
| Integración | PATCH |
/v1/webhook-endpoints/{id} |
Modificar URL, eventos o estado. | Obligatoria | webhook.endpoint_updated |
| Integración | DELETE |
/v1/webhook-endpoints/{id} |
Desactivar o eliminar. | Obligatoria | webhook.endpoint_deleted |
| Integración | POST |
/v1/webhook-endpoints/{id}/test |
Enviar evento de prueba. | Obligatoria | webhook.test_sent |
| Integración | POST |
/v1/webhook-endpoints/{id}/rotate-secret |
Rotar secreto. | Obligatoria | webhook.secret_rotated |
| Integración | POST |
/v1/webhook-endpoints/{id}/pause |
Pausar entregas. | Obligatoria | webhook.endpoint_paused |
| Integración | POST |
/v1/webhook-endpoints/{id}/resume |
Reanudar. | Obligatoria | webhook.endpoint_resumed |
Ejemplo: registrar receptor
POST /v1/webhook-endpoints
Idempotency-Key: 0846d8ad-a8a3-49bc-a7e8-f9d8f839783b
{
"url": "https://api.cliente.com/webhooks/yupy",
"events": [
"payment_instruction.presented",
"payment.reconciled",
"payment.amount_difference",
"payment.late_detected"
],
"active": true
}
{
"webhook_endpoint_id": "whe_01JXYZ",
"url": "https://api.cliente.com/webhooks/yupy",
"secret": "<SHOWN_ONCE>",
"status": "active",
"request_id": "req_01JXYZ"
}
Referencia opcional desde una Payment Order
{
"webhook_endpoint_id": "whe_01JXYZ"
}
El ID debe pertenecer a la empresa y estar autorizado. No se acepta una URL arbitraria en cada operación.
Entregas y reintentos
| Audiencia | Método | Ruta propuesta | Uso | Idempotencia | Eventos o callbacks relacionados |
|---|---|---|---|---|---|
| Integración | GET |
/v1/webhook-deliveries |
Listar intentos. | No | Ninguno |
| Integración | GET |
/v1/webhook-deliveries/{delivery_id} |
Consultar intento. | No | Ninguno |
| Integración | POST |
/v1/webhook-deliveries/{delivery_id}/retry |
Reintentar intento fallido. | Obligatoria | webhook.delivery_retried |
| Integración | POST |
/v1/webhook-events/{event_id}/redeliver |
Reenviar evento a receptores seleccionados. | Obligatoria | webhook.event_redelivered |
Solicitud enviada por YUPY
POST /webhooks/yupy HTTP/1.1
Content-Type: application/json
Yupy-Event-Id: evt_01JXYZ
Yupy-Timestamp: 1784567890
Yupy-Signature: v1=<HEX_SIGNATURE>
{
"event_id": "evt_01JXYZ",
"event_type": "payment.reconciled",
"event_version": "1.0",
"created_at": "2026-08-09T20:18:42-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"status": "confirmed",
"confirmed_at": "2026-08-09T20:18:42-05:00",
"closed_at": "2026-08-09T20:18:42-05:00"
}
}
payment_order_uid es la identidad canónica YUPY de la Payment Order y external_transaction_id conserva la correlación comercial con el sistema origen. payment.reconciled representa confirmación financiera normalizada por Reconciliation; por eso el ejemplo muestra una Payment Order terminal confirmed con confirmed_at y closed_at.
Eventos como payment.reported, payment.evidence_received o resultados de OCR son señales operativas/evidencia y no deben interpretarse como confirmación financiera por sí solos.
Evento de presentación de instrucciones
payment_instruction.presented registra que una instrucción de pago fue efectivamente presentada en el canal aplicable. No debe inferirse únicamente porque una entrega quedó encolada, enviada o recibida.
{
"event_id": "evt_01JPRESENTED",
"event_type": "payment_instruction.presented",
"event_version": "1.0",
"created_at": "2026-08-09T20:16:03-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"presented_at": "2026-08-09T20:16:02-05:00",
"delivery_channel": "web_checkout",
"ack_type": "checkout_rendered",
"external_reference": "ycs_01JXYZ"
}
}
El catálogo público de webhooks no tiene que reproducir uno a uno todos los eventos del Event Log interno de YUPY. El Event Log puede ser más rico; únicamente los eventos definidos como públicos forman parte de este contrato de webhook.
Respuesta esperada
HTTP/1.1 200 OK
{
"received": true
}
Firma propuesta
HMAC-SHA256(
webhook_secret,
timestamp + "." + raw_body
)
La firma se valida sobre el cuerpo original. Los nombres de headers y la tolerancia temporal permanecen propuestos hasta implementación.
Idempotencia de recepción
event_id ya procesado
→ no repetir efectos comerciales
→ responder 200
El receptor debe deduplicar por event_id. Un reintento o redelivery del mismo evento no debe ejecutar dos veces el efecto comercial asociado.
Catálogo inicial de eventos
payment_instruction.presented
payment.reported
payment.evidence_received
payment.evidence_processed
payment.detected
payment.reconciled
payment.amount_difference
payment.ambiguous
payment.late_detected
payment.review_required
payment.cancelled
payment.supplemental_order_created
checkout.created
checkout.opened
checkout.expired
checkout.revoked
shift.opened
shift.expiring
shift.closed
shift.auto_closed
delivery.queued
delivery.sent
delivery.delivered
delivery.failed
claim.created
claim.evidence_received
claim.resolved
case.created
case.resolved
refund.identified
refund.requested
refund.approved
refund.rejected
refund.pending_execution
refund.execution_failed
refund.completed
refund.cancelled
report.queued
report.ready
report.failed
Callbacks relacionados con endpoints principales
| Operación | Eventos posibles |
|---|---|
POST /v1/payment-orders |
payment.*, payment_instruction.presented, checkout.created, delivery.* |
POST /v1/payment-orders/{id}/payment-reported |
payment.reported |
POST /v1/payment-orders/{id}/evidence |
payment.evidence_received, payment.evidence_processed |
POST /v1/payment-orders/{id}/cancel |
payment.cancelled |
POST /v1/shifts |
shift.opened |
POST /v1/shifts/{id}/close |
shift.closed |
POST /v1/report-jobs |
report.queued, report.ready, report.failed |
POST /v1/refund-cases/{id}/execution-attempts |
refund.completed o refund.execution_failed |
Errores principales
| HTTP | Código | Descripción |
|---|---|---|
| 404 | webhook_endpoint_not_found |
Receptor inexistente. |
| 401 | webhook_signature_invalid |
Firma inválida en el receptor. |
| 401 | webhook_timestamp_invalid |
Timestamp fuera de tolerancia. |
| 404 | webhook_delivery_not_found |
Intento inexistente. |
| 409 | webhook_retry_not_allowed |
El intento no puede repetirse. |
| 422 | webhook_url_invalid |
URL no válida o no autorizada. |
Changelog — Payment Order / API Engine
2026-08-09 — Payment Order / API Engine v1.0: el payload de payment.reconciled pasó de la identidad histórica yupy_transaction_id/ypt_... a payment_order_uid/pay_...; se retiró el objeto histórico state.operational/financial del payload activo y se documentó la Payment Order terminal confirmed con confirmed_at y closed_at; se aclaró que payment.reported, evidencia y OCR no son confirmación financiera; se incorporó el evento público payment_instruction.presented con presented_at, delivery_channel, ack_type y external_reference; y se explicitó que el catálogo público de webhooks puede ser un subconjunto del Event Log interno.