Webhooks
ONVO usa webhooks para notificar a tu aplicación cuando se produce un resultado procesando una intención de pago, un cargo recurrente, una sesión de checkout o una transferencia entrante.
Para recibirlos, configurá una URL de callback en tu cuenta de ONVO. Enviaremos una petición POST a esa URL cada vez que se procese un evento soportado. El payload incluye un atributo type, que identifica el evento, y un objeto data con la información relacionada.
Referencia API relacionada
Flujo recomendado
- Exponé un endpoint HTTPS.
- Registrá el endpoint en el Dashboard de ONVO, en la sección de desarrolladores.
- Verificá el origen del evento con el secret del webhook.
- Procesá el evento de forma idempotente.
- Respondé con estado
2xxcuando el procesamiento sea correcto.
Eventos soportados
| Evento | Cuándo ocurre |
|---|---|
payment-intent.succeeded | Una intención de pago se procesa con éxito. |
payment-intent.failed | Una intención de pago falla. |
payment-intent.deferred | Una intención de pago queda a la espera de aprobación, por ejemplo en un flujo SINPE. |
subscription.renewal.succeeded | Un cargo recurrente se renueva con éxito. |
subscription.renewal.failed | La renovación de un cargo recurrente falla. |
checkout-session.succeeded | Una sesión de checkout se procesa con éxito. |
mobile-transfer.received | Se recibe una transferencia entrante en un número SINPE Móvil personalizado. |
mobile-transfer.received es una notificación adicional para comercios con esta funcionalidad activa. No representa por sí sola un pago satisfactorio y no se envía por defecto. Para habilitarla, contactá a soporte.
Formato del payload
ONVO envía todos los webhooks como POST con Content-Type: application/json. El cuerpo siempre tiene esta estructura:
{
"type": "payment-intent.succeeded",
"data": {
"id": "clpiment0001"
}
}
| Campo | Tipo | Descripción |
|---|---|---|
type | string | Nombre del evento recibido. Usalo para decidir qué lógica ejecutar. |
data | object | Snapshot del objeto relacionado con el evento. Su forma depende del valor de type. |
Los montos se envían como enteros en la unidad mínima de la moneda. Por ejemplo, 500000 representa ₡5,000.00 en CRC y 5000 representa $50.00 en USD. Las fechas se envían en formato ISO 8601 en UTC. Los ejemplos muestran los campos más importantes para integrar; según el método de pago o el flujo, data puede incluir campos adicionales o valores null.
Payloads por evento
payment-intent.succeeded
data contiene la intención de pago actualizada. Cuando aplica, incluye el cliente y la lista de cargos generados para esa intención.
{
"type": "payment-intent.succeeded",
"data": {
"id": "clpiment0001",
"accountId": "clacct0001",
"mode": "test",
"amount": 500000,
"baseAmount": 500000,
"baseExchangeRate": 1,
"capturableAmount": 0,
"receivedAmount": 500000,
"currency": "CRC",
"status": "succeeded",
"confirmationAttempts": 1,
"description": "Orden #1001",
"paymentMethodId": "clpm0001",
"customerId": "clcus0001",
"metadata": {
"orderId": "order_1001"
},
"customer": {
"id": "clcus0001",
"name": "Ana Mora",
"email": "ana@example.com",
"phone": "+50688888888"
},
"charges": [
{
"id": "clch0001",
"amount": 500000,
"baseAmount": 500000,
"baseExchangeRate": 1,
"refNumber": "123456",
"mode": "test",
"currency": "CRC",
"status": "succeeded",
"failureCode": null,
"failureMessage": null,
"isApproved": true,
"isCaptured": true,
"createdAt": "2026-04-29T16:10:00.000Z",
"updatedAt": "2026-04-29T16:10:01.000Z"
}
],
"lastPaymentError": null,
"createdAt": "2026-04-29T16:09:50.000Z",
"updatedAt": "2026-04-29T16:10:01.000Z"
}
}
payment-intent.failed
data contiene la intención de pago y un objeto error con el motivo del fallo.
{
"type": "payment-intent.failed",
"data": {
"id": "clpiment0002",
"accountId": "clacct0001",
"currency": "CRC",
"capturableAmount": 0,
"status": "requires_payment_method",
"metadata": {
"orderId": "order_1002"
},
"customer": {
"id": "clcus0002",
"name": "Luis Vega",
"email": "luis@example.com",
"phone": "+50688887777"
},
"error": {
"id": "clerr0001",
"mode": "test",
"paymentMethodType": "card",
"type": "processing_error",
"code": "declined",
"message": "Transaction declined due to test payment method used",
"createdAt": "2026-04-29T16:12:00.000Z",
"updatedAt": "2026-04-29T16:12:00.000Z"
}
}
}
payment-intent.deferred
data contiene la intención de pago en estado pendiente de confirmación externa, por ejemplo cuando ONVO espera confirmación de una transferencia.
{
"type": "payment-intent.deferred",
"data": {
"id": "clpiment0003",
"accountId": "clacct0001",
"mode": "test",
"amount": 250000,
"currency": "CRC",
"status": "processing",
"paymentMethodId": "clpm0003",
"customerId": "clcus0003",
"metadata": {
"orderId": "order_1003"
},
"createdAt": "2026-04-29T16:13:00.000Z",
"updatedAt": "2026-04-29T16:13:02.000Z"
}
}
subscription.renewal.succeeded
data contiene la renovación asociada al período cobrado. Cada renovación está ligada a un cargo recurrente y a la intención de pago creada para ese período.
{
"type": "subscription.renewal.succeeded",
"data": {
"id": "clinv0001",
"accountId": "clacct0001",
"mode": "test",
"status": "paid",
"currency": "CRC",
"attemptCount": 1,
"attempted": true,
"description": "Plan mensual",
"total": 1200000,
"subTotal": 1200000,
"originalTotal": null,
"periodStart": "2026-04-01T00:00:00.000Z",
"periodEnd": "2026-05-01T00:00:00.000Z",
"paymentIntentId": "clpiment0004",
"subscriptionId": "clsub0001",
"customerId": "clcus0004",
"metadata": {
"plan": "pro"
},
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-01T00:00:05.000Z"
}
}
subscription.renewal.failed
data contiene el estado de la renovación, el estado del cargo recurrente, la próxima fecha de intento y el error recibido al procesar la intención de pago.
{
"type": "subscription.renewal.failed",
"data": {
"subscriptionId": "clsub0002",
"paymentIntentId": "clpiment0005",
"currency": "CRC",
"lastPaymentAttempt": "2026-04-29T16:20:00.000Z",
"attemptCount": 2,
"invoiceStatus": "open",
"subscriptionStatus": "past_due",
"metadata": {
"accountTier": "pro"
},
"invoiceMetadata": {
"period": "2026-04"
},
"invoicePeriodStart": "2026-04-01T00:00:00.000Z",
"invoicePeriodEnd": "2026-05-01T00:00:00.000Z",
"currentPeriodStart": "2026-04-01T00:00:00.000Z",
"currentPeriodEnd": "2026-05-01T00:00:00.000Z",
"nextPaymentAttempt": "2026-04-30T16:20:00.000Z",
"customer": {
"id": "clcus0005",
"name": "María Solís",
"email": "maria@example.com",
"phone": "+50688886666"
},
"error": {
"id": "clerr0002",
"mode": "test",
"paymentMethodType": "card",
"type": "processing_error",
"code": "declined",
"message": "Transaction declined due to test payment method used",
"createdAt": "2026-04-29T16:20:00.000Z",
"updatedAt": "2026-04-29T16:20:00.000Z"
}
}
}
checkout-session.succeeded
data contiene la sesión de Checkout completada, sus ítems y el cliente tomado de la intención de pago asociada.
{
"type": "checkout-session.succeeded",
"data": {
"id": "clcs0001",
"url": "https://checkout.onvopay.com/pay/clcs0001",
"mode": "test",
"status": "complete",
"paymentStatus": "paid",
"paymentMode": "payment",
"amountSubTotal": 350000,
"amountTotal": 350000,
"currency": "CRC",
"successUrl": "https://example.com/success",
"cancelUrl": "https://example.com/cancel",
"paymentIntentId": "clpiment0006",
"customerId": "clcus0006",
"metadata": {
"cartId": "cart_123"
},
"customer": {
"id": "clcus0006",
"name": "Sofía Rojas",
"email": "sofia@example.com",
"phone": "+50688885555"
},
"lineItems": [
{
"name": "Producto de prueba",
"description": "Descripción del producto",
"currency": "CRC",
"amount": 350000,
"priceId": "clprice0001"
}
],
"createdAt": "2026-04-29T16:25:00.000Z",
"updatedAt": "2026-04-29T16:25:10.000Z"
}
}
mobile-transfer.received
data contiene la información de la transferencia entrante. Este evento no confirma una intención de pago por sí solo.
{
"type": "mobile-transfer.received",
"data": {
"amount": 1450000,
"currency": "CRC",
"description": "PAGO DE SERVICIOS",
"SINPERefNumber": "2025110312774577852010",
"authorizationDate": "2026-04-29T16:30:00.000Z",
"originId": "01-1393-1919",
"originName": "JUAN PEREZ CASTRO",
"originPhone": "72940567"
}
}
Seguridad
Cada webhook tiene un secret asignado. Podés verlo en el Dashboard de ONVO junto a la configuración del webhook.
ONVO incluye ese valor en el header X-Webhook-Secret. El backend genera secrets con el prefijo webhook_secret_. Usalo para verificar que la solicitud viene de ONVO antes de procesar el evento.
X-Webhook-Secret: webhook_secret_...
Respuestas y errores
Cuando un evento representa un error, el objeto del payload puede incluir un atributo error. Ese objeto puede contener:
| Campo | Descripción |
|---|---|
message | Descripción legible del error. |
code | Código del error, cuando aplica. |
type | Tipo de error, cuando aplica. |
Tu endpoint debe responder con un código 2xx solamente cuando el evento fue recibido y procesado correctamente. Si respondés con otro estado, ONVO registra la entrega como fallida.
Tu sistema debe tolerar eventos recibidos fuera de orden.