Webhooks
ONVO uses webhooks to notify your application when a result is produced while processing a payment intent, subscription, checkout session, or incoming transfer.
To receive them, configure a callback URL in your ONVO account. ONVO sends a POST request to that URL whenever a supported event is processed. The payload includes a type attribute that identifies the event and a data object with the related information.
Related API reference
Recommended flow
- Expose an HTTPS endpoint.
- Register the endpoint in the ONVO Dashboard under developers.
- Verify the event origin with the webhook secret.
- Process the event idempotently.
- Respond with a
2xxstatus when processing succeeds.
Supported events
| Event | When it happens |
|---|---|
payment-intent.succeeded | A payment intent is processed successfully. |
payment-intent.failed | A payment intent fails. |
payment-intent.deferred | A payment intent is waiting for approval, for example in a SINPE flow. |
subscription.renewal.succeeded | A subscription renews successfully. |
subscription.renewal.failed | A subscription renewal fails. |
checkout-session.succeeded | A checkout session is processed successfully. |
mobile-transfer.received | An incoming transfer is received on a custom SINPE Movil number. |
mobile-transfer.received is an additional notification for merchants with this capability enabled. It does not represent a successful payment by itself and is not sent by default. Contact support to enable it.
Payload format
ONVO sends every webhook as a POST request with Content-Type: application/json. The body always has this structure:
{
"type": "payment-intent.succeeded",
"data": {
"id": "clpiment0001"
}
}
| Field | Type | Description |
|---|---|---|
type | string | Name of the event received. Use it to decide which logic to run. |
data | object | Snapshot of the object related to the event. Its shape depends on type. |
Amounts are sent as integers in the smallest currency unit. For example, 500000 represents ₡5,000.00 in CRC and 5000 represents $50.00 in USD. Dates are sent as ISO 8601 timestamps in UTC. The examples show the most important fields for integrations; depending on the payment method or flow, data can include additional fields or null values.
Payloads by event
payment-intent.succeeded
data contains the updated payment intent. When available, it includes the customer and the list of charges generated for that intent.
{
"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": "Order #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 contains the payment intent and an error object with the failure reason.
{
"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 contains the payment intent while it is waiting for external confirmation, for example when ONVO is waiting for a transfer confirmation.
{
"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 contains the renewal associated with the charged period. Each renewal is tied to a subscription and to the payment intent created for that period.
{
"type": "subscription.renewal.succeeded",
"data": {
"id": "clinv0001",
"accountId": "clacct0001",
"mode": "test",
"status": "paid",
"currency": "CRC",
"attemptCount": 1,
"attempted": true,
"description": "Monthly plan",
"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 contains the renewal status, the subscription status, the next retry date, and the error returned while processing the payment intent.
{
"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 contains the completed Checkout Session, its line items, and the customer from the associated payment intent.
{
"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": "Test product",
"description": "Product description",
"currency": "CRC",
"amount": 350000,
"priceId": "clprice0001"
}
],
"createdAt": "2026-04-29T16:25:00.000Z",
"updatedAt": "2026-04-29T16:25:10.000Z"
}
}
mobile-transfer.received
data contains the incoming transfer information. This event does not confirm a payment intent by itself.
{
"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"
}
}
Security
Each webhook has an assigned secret. You can view it in the ONVO Dashboard next to the webhook configuration.
ONVO includes that value in the X-Webhook-Secret header. The backend generates secrets with the webhook_secret_ prefix. Use it to verify that the request comes from ONVO before processing the event.
X-Webhook-Secret: webhook_secret_...
Responses and errors
When an event represents an error, the payload object can include an error attribute. That object can contain:
| Field | Description |
|---|---|
message | Human-readable error description. |
code | Error code, when available. |
type | Error type, when available. |
Your endpoint should respond with a 2xx status only when the event was received and processed correctly. If you respond with another status, ONVO records the delivery as failed.
Your system should tolerate events received out of order.