Eventos y payloads de webhooks
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.
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.
Contrato productivo certificado: payment_order.terminal utiliza únicamente result = pagado | cancelado | timeout. Un fallo técnico de entrega de webhook puede terminar como failed en la infraestructura de delivery, pero eso no cambia el estado comercial de la Payment Order.
Estado documental: Contrato propuesto. Los nombres y versiones de eventos se confirmarán con la implementación.
Estructura común
{
"event_id": "evt_01JXYZ",
"event_type": "payment.reconciled",
"event_version": "1.0",
"created_at": "2026-07-20T14:40:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"amount": "85.50",
"currency": "PEN",
"state": {
"operational": "active",
"financial": "reconciled"
}
}
}
Contexto opcional
Cuando fue enviado en la creación:
{
"context": {
"custom_reference_1": "ROUTE-184",
"custom_reference_2": "SEAT-12A"
}
}
Cuando no fue enviado, puede omitirse.
payment.reported
{
"event_id": "evt_01J001",
"event_type": "payment.reported",
"event_version": "1.0",
"created_at": "2026-07-20T14:35:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"reported_by": "seller",
"state": {
"operational": "active",
"financial": "payment_reported"
}
}
}
No confirma el pago.
payment.evidence_received
{
"event_id": "evt_01J002",
"event_type": "payment.evidence_received",
"event_version": "1.0",
"created_at": "2026-07-20T14:36:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"evidence_id": "evi_01JXYZ",
"ocr": {
"status": "queued"
},
"state": {
"operational": "active",
"financial": "payment_reported"
}
}
}
payment.detected
{
"event_id": "evt_01J003",
"event_type": "payment.detected",
"event_version": "1.0",
"created_at": "2026-07-20T14:38:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"expected_amount": "85.50",
"detected_amount": "85.50",
"currency": "PEN",
"state": {
"operational": "active",
"financial": "movement_detected"
}
}
}
Movimiento detectado no siempre significa conciliación terminada.
payment.reconciled
{
"event_id": "evt_01J004",
"event_type": "payment.reconciled",
"event_version": "1.0",
"created_at": "2026-07-20T14:40:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"expected_amount": "85.50",
"reconciled_amount": "85.50",
"currency": "PEN",
"state": {
"operational": "active",
"financial": "reconciled"
},
"reconciled_at": "2026-07-20T14:40:00-05:00"
}
}
payment.amount_difference
{
"event_id": "evt_01J005",
"event_type": "payment.amount_difference",
"event_version": "1.0",
"created_at": "2026-07-20T14:40:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"expected_amount": "85.50",
"received_amount": "80.00",
"difference_amount": "5.50",
"difference_type": "underpayment",
"currency": "PEN",
"state": {
"operational": "active",
"financial": "amount_difference"
}
}
}
payment.ambiguous
{
"event_id": "evt_01J006",
"event_type": "payment.ambiguous",
"event_version": "1.0",
"created_at": "2026-07-20T14:41:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"state": {
"operational": "active",
"financial": "ambiguous"
},
"action_required": "review"
}
}
payment.late_detected
{
"event_id": "evt_01J007",
"event_type": "payment.late_detected",
"event_version": "1.0",
"created_at": "2026-07-20T15:10:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"expected_amount": "85.50",
"detected_amount": "85.50",
"currency": "PEN",
"detected_at": "2026-07-20T15:10:00-05:00",
"state": {
"operational": "expired",
"financial": "late_detected"
},
"action_required": "client_review"
}
}
payment.review_required
{
"event_id": "evt_01J008",
"event_type": "payment.review_required",
"event_version": "1.0",
"created_at": "2026-07-20T15:11:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"reason": "insufficient_matching_evidence",
"state": {
"operational": "active",
"financial": "review_required"
}
}
}
payment.cancelled
{
"event_id": "evt_01J009",
"event_type": "payment.cancelled",
"event_version": "1.0",
"created_at": "2026-07-20T14:36:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"reason": "customer_cancelled",
"state": {
"operational": "cancelled",
"financial": "awaiting_payment"
}
}
}
checkout.expired
{
"event_id": "evt_01J010",
"event_type": "checkout.expired",
"event_version": "1.0",
"created_at": "2026-07-20T14:45:02-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"checkout_session_id": "ycs_01JXYZ",
"expires_at": "2026-07-20T14:45:02-05:00",
"state": {
"operational": "expired",
"financial": "awaiting_payment"
}
}
}
refund.identified
{
"event_id": "evt_01J011",
"event_type": "refund.identified",
"event_version": "1.0",
"created_at": "2026-07-20T15:20:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"refund_case_id": "rfc_01JXYZ",
"amount_to_refund": "4.50",
"currency": "PEN",
"reason": "overpayment",
"refund_executor": "client_company",
"state": {
"refund": "refund_identified"
}
}
}
YUPY identifica e informa. La empresa ejecuta la devolución.
refund.pending_execution
{
"event_id": "evt_01J012",
"event_type": "refund.pending_execution",
"event_version": "1.0",
"created_at": "2026-07-20T15:25:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"refund_case_id": "rfc_01JXYZ",
"amount_to_refund": "4.50",
"currency": "PEN",
"state": {
"refund": "refund_pending_execution"
}
}
}
refund.completed
Se emite cuando la empresa registra la ejecución y YUPY acepta la evidencia correspondiente.
{
"event_id": "evt_01J013",
"event_type": "refund.completed",
"event_version": "1.0",
"created_at": "2026-07-20T16:10:00-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"refund_case_id": "rfc_01JXYZ",
"refunded_amount": "4.50",
"currency": "PEN",
"executed_by": "client_company",
"state": {
"refund": "refund_completed"
}
}
}
Estados separados
Operativos
created
active
expired
cancelled
closed
Financieros
awaiting_payment
payment_reported
movement_detected
reconciliation_pending
reconciled
amount_difference
ambiguous
late_detected
review_required
Entrega
queued
sent
delivered
read
failed
Devolución
refund_identified
refund_requested
refund_under_review
refund_approved
refund_pending_execution
refund_completed
refund_rejected
refund_failed
refund_cancelled
Un mensaje entregado no significa pago confirmado. Una devolución aprobada no significa que ya fue ejecutada.
Versionamiento
Cada evento incluye event_version. Un cambio incompatible debe generar una nueva versión.
Cierre del bloque
La API crea y consulta operaciones.
El SDK presenta Web Checkout.
El chat permite operar POS vía chat.
YUPY consulta internamente las cuentas receptoras.
YUPY concilia.
Los webhooks informan al sistema de la empresa.
La biblioteca completa de endpoints, reportes, callbacks y errores se documentará en el siguiente bloque.
Criterios de aceptación
- Todos los eventos tienen
event_id. - Los payloads tienen versión.
- El contexto se devuelve solo cuando existe.
payment.reportedno confirma el pago.payment.reconciledrepresenta conciliación completada.- Los pagos tardíos se notifican expresamente.
- Las diferencias incluyen montos.
- Las cancelaciones mantienen estado financiero separado.
- YUPY no se presenta como ejecutor de devoluciones.
- Los estados operativos, financieros, de entrega y devolución permanecen separados.
Payment Order / API Engine v1.0
Los webhooks públicos mantienen la disciplina existente de event_id, event_type, event_version y created_at. La identidad canónica de la orden pasa a ser payment_order_uid; external_transaction_id continúa representando la identidad comercial del sistema origen.
payment_instruction.presented
Este evento representa la presentación efectiva de la instrucción de pago al pagador. Preparar una URL, resolver un QR o construir un payload no es suficiente: el canal debe confirmar que la instrucción fue realmente mostrada o entregada según su mecanismo disponible.
{
"event_id": "evt_01JPRESENTED",
"event_type": "payment_instruction.presented",
"event_version": "1.0",
"created_at": "2026-08-09T20:16:04-05:00",
"data": {
"payment_order_uid": "pay_01JXYZ",
"external_transaction_id": "ORDER-10482",
"presented_at": "2026-08-09T20:16:04-05:00",
"delivery_channel": "web",
"ack_type": "rendered",
"external_reference": "ycs_01JXYZ"
}
}
Ejemplos de ack_type dependen del canal: Web puede usar rendered o displayed; POS o kiosco pueden registrar displayed o printed; un canal conversacional utiliza el mejor acknowledgement verificable disponible.
payment.reported no confirma el pago
Se conserva el principio existente: payment.reported expresa un claim operativo. Un vendedor o comprador puede informar que el pago fue realizado, pero esa señal debe disparar verificación y no debe marcar por sí sola la Payment Order como confirmada.
Del mismo modo, payment.evidence_received informa que YUPY recibió evidencia —por ejemplo una fotografía/captura— y puede iniciar OCR o verificación. Tampoco constituye confirmación financiera por sí sola.
payment.reconciled representa confirmación financiera normalizada
Cuando Reconciliation encuentra y normaliza evidencia suficiente del pago, el resultado puede producir payment.reconciled. En Payment Order v1 ese resultado lleva la orden al lifecycle terminal confirmed y permite registrar confirmed_at.
claim / evidencia
↓
verificación
↓
Reconciliation
↓
payment.reconciled
↓
Payment Order = confirmed
confirmed_at
Counter released
payment.late_detected
Se conserva el evento de pago tardío. Una Payment Order puede haber expirado o sido cancelada, haber liberado su Counter y posteriormente recibir evidencia histórica de un pago. payment.late_detected registra ese hecho financiero sin reabrir la orden original y sin volver a ocupar su Counter.
Event Log interno y webhooks públicos son capas distintas
El Payment Order Orchestrator y sus componentes pueden registrar un Event Log interno más detallado:
ORDER_CREATED
COUNTER_ACQUIRED
INSTRUMENT_SELECTED
DELIVERY_PREPARED
PAYMENT_INSTRUCTION_PRESENTED
SELLER_CLAIMS_PAID
SELLER_PHOTO_RECEIVED
BUYER_CLAIMS_PAID
VERIFICATION_SCHEDULED
VERIFICATION_STARTED
VERIFICATION_NOT_FOUND
VERIFICATION_AMBIGUOUS
PAYMENT_CONFIRMED
ORDER_EXPIRED
ORDER_CANCELLED
ORDER_FAILED
COUNTER_RELEASED
ORDER_CLOSED
No existe una relación obligatoria uno-a-uno entre Event Log interno y webhook público. Algunos eventos son exclusivamente operativos o técnicos. El catálogo público debe evolucionar de manera versionada y publicar únicamente los eventos que formen parte del contrato externo.
Correlación
Los consumidores deben conservar event_id para deduplicación y payment_order_uid para correlacionar todos los eventos de la misma Payment Order. external_transaction_id mantiene la correlación con el sistema comercial origen.
Changelog — Payment Order / API Engine
2026-08-09 — Payment Order / API Engine v1.0: se renombró en los payloads públicos la identidad canónica yupy_transaction_id a payment_order_uid; se incorporó payment_instruction.presented con presented_at; se preservó expresamente que payment.reported y la evidencia recibida no confirman financieramente el pago; se vinculó payment.reconciled con la confirmación financiera normalizada; se mantuvo payment.late_detected sin reapertura ni reocupación del Counter; y se documentó que el Event Log interno es más rico que el catálogo de webhooks públicos.