Webhooks del API Bancario
API 1.0.0El grupo Webhooks permite registrar el destino HTTPS donde recibís los eventos, ver qué
features podés suscribir por método de pago y consultar el contrato de payload de cada
evento antes de escribir el receptor.
POST /api/public/v1/webhook/destination
GET /api/public/v1/webhook/features
GET /api/public/v1/webhook/event-contracts/{event_type}Eventos#
El spec declara cuatro tipos de evento:
| Evento | Cuándo llega |
|---|---|
payin.posted | Se acreditó un movimiento de entrada. |
payment.succeeded | El pago terminó de forma exitosa. |
payment.failed | El pago terminó con falla. |
payment.reversed | El pago fue revertido. |
Un pago revertido conserva el estado posted y se distingue por result: reversed y
has_reversal: true; ver estados del pago.
Buenas prácticas del receptor#
Respondé 2xx en cuanto recibís el evento y procesá aparte: un receptor lento provoca
reenvíos. Tratá los eventos como potencialmente repetidos y deduplicá por el identificador
del pago. Y no confíes el estado final sólo al webhook: confirmalo con
la consulta del pago.
Operaciones#
Create webhook destination
POST /api/public/v1/webhook/destination
Host: https://api-baas-sandbox.tilopay.com — requiere Authorization: Bearer <access_token>.
Valid fields depend on destination_kind and scope_kind. Send only the fields that apply to your combination; other scope fields must be omitted (not null placeholders).
Event payloads Inspect outbound contracts with GET /api/public/v1/webhook/event-contracts/{event_type} (payin.posted, payment.succeeded, payment.failed, payment.reversed).
destination_kind
EXTERNAL_WEBHOOK(only kind available on the public API): requireswebhook_url(must start withhttps://) andsecret_ref. Optional delivery tuning (timeout_ms,max_attempts). Payloads are always encrypted withAES_GCMusingsecret_ref; the HTTP body is{"data":"<ciphertext>"}.INTERNALis reserved for platform administration and cannot be created through this endpoint.
scope_kind
TENANT: omitscope_account_id,scope_owner_type, andscope_owner_id.ACCOUNT: requirescope_account_id; omit owner fields.OWNER: requirescope_owner_typeandscope_owner_id; omitscope_account_id.scope_owner_typeis one oftenant,partner,user,platform,customer(lowercase in payload).
Webhook encryption (EXTERNAL_WEBHOOK)
Deliveries always send Content-Type: application/json and Accept: application/json.
The HTTP body is always {"data": "<base64>"}. The base64-decoded bytes are:
nonce (12 bytes, random) || AES-256-GCM ciphertext || 16-byte auth tag
key = SHA256(secret_ref) # 32 bytes → AES-256 wire = base64_decode(data) nonce, sealed = wire[:12], wire[12:] # sealed = ciphertext || tag plaintext = AES-GCM-Open(key, nonce, sealed, additional_data = none)
No additional authenticated data (AAD) is used. plaintext is the same JSON envelope documented per event type in GET /webhook/event-contracts/{event_type} — decrypt first, then apply that contract. Authenticity is provided by the GCM auth tag (decryption fails if the body was tampered with).
Deduplicate retries using event.event_id in the payload envelope — delivery retries can redeliver the same event with the same event_id.
Parámetros
| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
X-Correlation-Id | header | string | — | Optional client-supplied correlation id for end-to-end tracing.
Echoed back as correlation_id in the response envelope. If omitted,
the API generates one and still returns it. |
Cuerpo del request
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
feature_id | string (uuid) | sí | Feature to subscribe (from GET /api/public/v1/webhook/features). Must exist or create fails. |
is_enabled | boolean | — | Defaults to true if omitted. |
destination_kind | string | sí | Public API accepts only EXTERNAL_WEBHOOK (HTTPS callback with shared secret). INTERNAL destinations are managed administratively.Valores: EXTERNAL_WEBHOOK |
destination_ref | string | — | Not used on the public API. Reserved for internal destinations. |
webhook_url | string (uri) | — | Required (must use https://). |
secret_ref | string | — | Required shared secret used to decrypt AES_GCM payloads (key = SHA256(secret_ref)).
See "Webhook encryption" in this operation's description. |
signature_algo | string | — | Fixed to AES_GCM for EXTERNAL_WEBHOOK. Omitted on create; stored as AES_GCM.
HMAC_SHA256 is no longer supported.Valores: AES_GCM |
replay_window_sec | integer | — | Reserved for legacy destinations. Not used for EXTERNAL_WEBHOOK AES_GCM deliveries. |
timeout_ms | integer | — | HTTP timeout for delivery attempts (EXTERNAL_WEBHOOK). |
max_attempts | integer | — | Max delivery attempts (EXTERNAL_WEBHOOK). |
backoff_policy | object | — | Optional free-form JSON retry/backoff override, stored as-is and not currently validated or documented by the platform. Omit to use the platform's default backoff policy. |
scope_kind | string | sí | - TENANT: no scope id fields.
- ACCOUNT: set scope_account_id only.
- OWNER: set scope_owner_type and scope_owner_id only.Valores: TENANT, ACCOUNT, OWNER |
scope_account_id | string (uuid) | — | Required when scope_kind is ACCOUNT. Must be omitted for TENANT and OWNER. |
scope_owner_type | string | — | Required when scope_kind is OWNER. Allowed values (lowercase) tenant, partner, user, platform, customer. Omit for TENANT and ACCOUNT. |
scope_owner_id | string (uuid) | — | Required when scope_kind is OWNER. Omit for TENANT and ACCOUNT. |
Ejemplo de request
external_webhook_tenant
{
"feature_id": "11111111-1111-1111-1111-111111111111",
"destination_kind": "EXTERNAL_WEBHOOK",
"scope_kind": "TENANT"
}Respuesta 201
The resource was created successfully.
response_code: CREATED
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
created_at | string | — | — |
destination_kind | string | sí | — |
destination_ref | string | — | — |
feature_id | string | sí | — |
id | string | sí | — |
is_enabled | boolean | sí | — |
max_attempts | integer | — | — |
replay_window_sec | integer | sí | — |
scope_account_id | string | — | — |
scope_kind | string | sí | — |
scope_owner_id | string | — | — |
scope_owner_type | string | — | — |
secret_ref | string | — | — |
signature_algo | string | sí | — |
timeout_ms | integer | — | — |
updated_at | string | — | — |
webhook_url | string | — | — |
Respuestas de error
| HTTP | response_code | Descripción |
|---|---|---|
| 400 | INVALID_REQUEST | Invalid request. Check the required fields and try again. |
| 401 | UNAUTHORIZED | Unauthorized. Verify your session or credentials. |
| 403 | FORBIDDEN | You do not have permission to perform this action. |
| 429 | TOO_MANY_REQUESTS | Too many requests. Please retry after a short delay. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. Please try again later. |
| 503 | SERVICE_UNAVAILABLE | A required service is temporarily unavailable. Please try again later. |
List webhook features by payment method
GET /api/public/v1/webhook/features
Host: https://api-baas-sandbox.tilopay.com — requiere Authorization: Bearer <access_token>.
Returns feature IDs and the event_types each feature can emit. Tenant scope is taken from the access token; clients must not send tenant_id.
Naming: event_types use payment.* for resource-level outcomes (payment.succeeded, payment.failed, payment.reversed) and payin.posted for the inbound direction only (funds received). There is no payout.posted and no transaction.* events. The delivered JSON object is always payment.
Inspect payload shapes with GET /api/public/v1/webhook/event-contracts/{event_type} (payin.posted, payment.succeeded, payment.failed, payment.reversed).
Parámetros
| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
X-Correlation-Id | header | string | — | Optional client-supplied correlation id for end-to-end tracing.
Echoed back as correlation_id in the response envelope. If omitted,
the API generates one and still returns it. |
country_code | query | string | — | — |
payment_method_code | query | string | — | — |
webhook_enabled | query | boolean | — | — |
Respuesta 200
The request was processed successfully.
response_code: OK
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
items | array<object> | sí | — |
items[].event_types | array<string> | sí | — |
items[].id | string | sí | — |
Respuestas de error
| HTTP | response_code | Descripción |
|---|---|---|
| 401 | UNAUTHORIZED | Unauthorized. Verify your session or credentials. |
| 403 | FORBIDDEN | You do not have permission to perform this action. |
| 429 | TOO_MANY_REQUESTS | Too many requests. Please retry after a short delay. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. Please try again later. |
| 503 | SERVICE_UNAVAILABLE | A required service is temporarily unavailable. Please try again later. |
Webhook event contract
GET /api/public/v1/webhook/event-contracts/{event_type}
Host: https://api-baas-sandbox.tilopay.com — requiere Authorization: Bearer <access_token>.
Returns the outbound webhook payload contract for one public event type (schema_version 1.0.0). The envelope object is always payment (never payin, payout, or transaction).
When to use each name
- payment — resource and envelope. Outcome events:
payment.succeeded,payment.failed,payment.reversed(both PAYIN and PAYOUT). Correlate with REST/transactions/payments. - payin / payout — direction (
PAYINinbound,PAYOUToutbound).payin.postedis inbound-only (ledger posted / funds received). There is no publicpayout.posted. - transaction — REST path prefix only. Not used in webhook
event_typeor payload objects.
Supported event_type values:
payin.posted— inbound (PAYIN) payment reached public statusposted. Envelope field ispayment.payment.local_paymentis a JSON boolean (true/false), not a string.payment.succeeded— the provider confirmed success for a payment (PAYIN or PAYOUT; PIN or SINPE Móvil). Resource-level outcome, not direction-specific.payment.failed— processing ended in a definitive failure (publicstatus=failed), with structuredpayment.error(domain/platform). When the provider rejected the payment, the envelope also includesrejection. Not used for later reversals.payment.reversed— subsequent reversal of a payment that already progressed (typicallystatus=postedorconfirmed). RESTresult=reversedandhas_reversal=true. Processingstatusis unchanged. Not apayment.failed.
Unknown event_type values (including internal-only payment.status_changed) return 404.
SINPE rejection codes (rejection, only on payment.failed)
When event_type = payment.failed and the provider rejected the payment, the envelope includes rejection (same object and catalog as data.rejection on POST /accounts/validate). rejection.code is one of a fixed set of normalized semantics (see the WebhookRejection.code schema enum). Most rows in the table below do NOT get a distinct `code` — only a subset of reason_code values has a dedicated semantic; every other reason_code, even one listed below with a specific message, returns code = SINPE_REJECTED (e.g. reason_code = 21 "fondos insuficientes" still returns SINPE_REJECTED). A reason_code not listed here at all still produces a response — message falls back to a generic text and code falls back to SINPE_REJECTED.
Cuenta / perfil / límites
| Code | Message |
|---|---|
| 21 | Cuenta Cliente con fondos insuficientes |
| 22 | Cuenta Cliente no admite créditos |
| 23 | Cuenta Cliente cerrada |
| 24 | Cuenta Cliente inactiva |
| 25 | Cuenta Cliente no admite débitos |
| 26 | Cuenta Cliente no es de fondos |
| 27 | Moneda de la Cuenta Cliente no corresponde |
| 28 | Cuenta cliente no existe |
| 29 | Cuenta Cliente no registrada en el SINPE |
| 30 | Cuenta Cliente no habilitada para el servicio |
| 31 | Cuenta Cliente bloqueada |
| 32 | Id cliente destino no coincide con registrado en la entidad |
| 33 | Nombre del cliente destino no coincide con el registrado en la entidad |
| 34 | Cuenta Cliente en proceso de cierre |
| 35 | Cuenta Cliente embargada |
| 36 | Cuenta Cliente con retención judicial |
| 37 | Cuenta de expediente simplificado no permite el monto indicado |
| 38 | Límite transaccional de la Cuenta Cliente excedido |
| 39 | Cuenta Cliente incorrecta |
| 40 | IBAN de la cuenta destino inválido |
| 41 | IBAN de la cuenta origen inválido |
| 42 | Tipo de cuenta no permite la transacción |
| 43 | Cuenta Cliente no pertenece a la entidad indicada |
| 44 | Producto de la cuenta no admite el servicio |
| 45 | Cuenta Cliente en estado de cancelación |
| 46 | Cuenta Cliente restringida por política de la entidad |
| 47 | Cuenta Cliente no permite pagos inmediatos |
| 48 | Cuenta Cliente no permite SINPE Móvil |
| 49 | Titular de la cuenta destino fallecido |
| 50 | Cuenta Cliente consolidada o migrada |
| 51 | Identificación del cliente origen no coincide |
| 52 | Identificación del cliente destino inválida |
| 53 | Cliente destino no autorizado para recibir el pago |
| 54 | Cliente origen no autorizado para enviar el pago |
| 55 | Perfil del cliente origen no permite la transacción |
| 56 | Monto inferior al mínimo permitido |
| 57 | Monto superior al máximo permitido |
| 58 | Cantidad de transacciones diarias excedida |
| 59 | Cantidad de transacciones mensuales excedida |
| 60 | Límite acumulado diario excedido |
| 61 | Límite acumulado mensual excedido |
| 62 | Comisión no pudo ser aplicada |
| 63 | Tipo de cambio no disponible |
| 64 | Transacción rechazada por control de lavado de dinero |
| 65 | Transacción rechazada por listas de control |
| 66 | Transacción en revisión de cumplimiento |
| 67 | Documento de respaldo requerido no presente |
| 68 | Firma o autenticación inválida |
| 69 | Token o segundo factor inválido |
| 70 | Sesión de usuario expirada |
| 71 | Usuario no autorizado para el canal |
| 72 | Dispositivo no registrado |
| 73 | Geolocalización no permitida |
| 74 | Operación no soportada en la moneda indicada |
| 75 | Operación no soportada para el tipo de cliente |
| 76 | Problemas de comunicación |
| 77 | Tiempo de espera agotado en la entidad origen |
| 78 | Tiempo de espera agotado en el SINPE |
| 79 | Error interno de la entidad origen |
| 80 | Error interno de la entidad destino |
| 81 | Entidad origen no disponible |
| 82 | Entidad destino no encontrada |
| 83 | Problemas en la respuesta del destino |
| 84 | Respuesta de la entidad origen incorrecta |
| 85 | Mensaje con formato electrónico inválido |
| 86 | Versión del estándar electrónico no soportada |
| 87 | Campo obligatorio no informado |
| 88 | Campo con valor fuera de catálogo |
| 89 | Checksum o integridad del mensaje inválida |
| 90 | Referencia SINPE duplicada |
| 91 | Moneda no corresponde |
| 92 | Transacción no autorizada por entidad destino |
| 93 | Transacción no autorizada por cliente destino |
| 94 | Transacción no autorizada por entidad origen |
| 95 | Transacción no autorizada por cliente origen |
| 96 | Reverso no permitido para el estado de la transacción |
| 97 | Reverso ya aplicado |
| 98 | Confirmación no permitida para el estado de la transacción |
| 99 | Liquidación no permitida para el estado de la transacción |
| 100 | Entidad Destino no disponible para procesar en tiempo real |
| 101 | Entidad origen no disponible para procesar en tiempo real |
| 102 | Servicio PIN no habilitado para la entidad destino |
| 103 | Servicio PIN no habilitado para la entidad origen |
| 104 | Código de entidad destino inválido |
| 105 | Código de entidad origen inválido |
| 106 | Código de país de la entidad destino inválido |
| 107 | Código de país de la entidad origen inválido |
| 108 | Número de referencia interna inválido |
| 109 | Número de referencia SINPE inválido |
| 110 | Transacción no se encuentra en un estado que permita la consulta |
Compensación con entidad destino
| Code | Message |
|---|---|
| 201 | Tiempo respuesta excedido por la entidad destino |
| 202 | Respuesta de la entidad destino incorrecta según el estándar electrónico |
| 203 | Se recibió una excepción de la entidad destino |
| 204 | Error de comunicación con la entidad destino |
| 205 | Falló procesamiento en el SINPE |
| 206 | Transacción no autorizada por cliente destino |
| 207 | Perfil transaccional del cliente destino no permite recibir el pago |
| 208 | Falló la acreditación en la cuenta destino |
| 209 | Falló el débito en la cuenta origen |
| 210 | Conciliación de la transacción no fue posible |
Identificación
| Code | Message |
|---|---|
| 801 | Identificación inválida |
| 802 | Identificación del cliente origen no encontrada |
| 803 | Identificación del cliente destino no encontrada |
| 804 | Identificación no vigente |
| 805 | Identificación vencida |
| 806 | Identificación no corresponde al tipo indicado |
| 807 | Tipo de identificación inválido |
| 808 | País de la identificación no soportado |
| 809 | Identificación de menor de edad no permitida |
| 810 | Identificación de persona jurídica no permitida para el servicio |
Validación de formato / Core Bancario
| Code | Message |
|---|---|
| 1001 | Cuenta cliente activa |
| 1002 | El Id de cliente destino no cumple con el formato esperado por el SINPE |
| 1003 | El Id de cliente origen no fue informado |
| 1004 | El Id de cliente origen no cumple con el formato esperado por el SINPE |
| 1005 | Monto con formato inválido |
| 1006 | Moneda con formato inválido |
| 1007 | Problemas de comunicación con el Core Bancario |
| 1008 | Core Bancario no disponible |
| 1009 | El valor para el campo no puede ser nulo o infringir su longitud mínima o máxima |
| 1010 | El valor para el campo no corresponde al tipo de dato esperado |
| 1011 | El valor para el campo no corresponde al catálogo permitido |
| 1012 | Fecha con formato inválido |
| 1013 | Hora con formato inválido |
| 1014 | Número de referencia con formato inválido |
| 1015 | IBAN con formato inválido |
Canal / tipo de identificación
| Code | Message |
|---|---|
| 1040 | Canal no informado |
| 1041 | Canal inválido |
| 1042 | Canal no corresponde |
| 1043 | Canal no habilitado para la entidad |
| 1044 | Canal no habilitado para el servicio |
| 1045 | El formato de la identificación es inválido |
| 1046 | Tipo de identificación no informado |
| 1080 | Tipo de identificación no corresponde al cliente origen |
| 1081 | Tipo de identificación no corresponde al cliente destino |
| 1082 | Tipo de identificación no vigente |
| 1083 | Tipo de identificación no soportado por el servicio |
| 1084 | Tipo de identificación inválido |
| 1085 | Tipo de identificación no soportado por la entidad |
SINPE Móvil (monedero)
| Code | Message |
|---|---|
| 15300 | El número de teléfono origen indicado es inválido |
| 15301 | El número de teléfono origen no tiene activo el Servicio Monedero |
| 15302 | El número de teléfono destino indicado es inválido |
| 15303 | El número de teléfono destino no está registrado en el padrón móvil del BCCR |
| 15304 | No es posible inactivar el monedero indicado pues no existe |
| 15305 | El número de teléfono indicado ya se encuentra activo como monedero en el padrón local |
⚠️ This catalog is under review — some entries (notably 208 and 209) are known to be pending verification against the official SINPE source and may be corrected in a future revision without notice.
Delivery body is JSON. EXTERNAL_WEBHOOK HTTP body is always {"data":"<ciphertext>"} (AES-256-GCM, key = SHA256(secret_ref)). Decrypt to obtain the event envelope documented per GET /webhook/event-contracts/{event_type}. and the decrypted plaintext matches this contract.
Parámetros
| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
X-Correlation-Id | header | string | — | Optional client-supplied correlation id for end-to-end tracing.
Echoed back as correlation_id in the response envelope. If omitted,
the API generates one and still returns it. |
event_type | path | string | sí | —Valores: payin.posted, payment.succeeded, payment.failed, payment.reversed |
Respuesta 200
The request was processed successfully.
response_code: OK
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
description | string | sí | — |
event_type | string | sí | — |
example | object | sí | — |
schema_version | string | sí | — |
Respuestas de error
| HTTP | response_code | Descripción |
|---|---|---|
| 401 | UNAUTHORIZED | Unauthorized. Verify your session or credentials. |
| 403 | FORBIDDEN | You do not have permission to perform this action. |
| 404 | NOT_FOUND | The requested resource was not found. |
| 429 | TOO_MANY_REQUESTS | Too many requests. Please retry after a short delay. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. Please try again later. |
| 503 | SERVICE_UNAVAILABLE | A required service is temporarily unavailable. Please try again later. |
Contratos de payload#
Cada evento declara su propio contrato de campos. Estos son los que el spec publica:
payin.posted
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
schema_version | string | sí | — |
event | object | sí | — |
event.event_id | string (uuid) | sí | — |
event.event_type | string | sí | —Valores: payin.posted |
event.occurred_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
event.correlation_id | string | sí | — |
context | object | sí | — |
context.country_code | string | sí | — |
context.payment_method_code | string | sí | Public payment method catalog. Same values as REST (PIN, SINPE_MOVIL). Internal codes such as PM_PIN are never exposed.Valores: PIN, SINPE_MOVIL |
payment | object | sí | — |
payment.payment_id | string (uuid) | sí | — |
payment.public_id | integer (int64) | sí | — |
payment.status | string | sí | Public payment status catalog. Always lowercase. Same values on REST
(create, list, get) and webhooks (payment.status).
Internal lifecycle states are collapsed:
- pending: initiated, validated, accepted
- processing: processing, pending_processing, posting
- confirmed: confirmed by the rail, not yet ledger-posted
- posted: ledger posted (stays posted after a later reversal)
- failed: failed, rejected, posting_failed, cancelled, expired
A reversal is not a status. Use REST result=reversed / has_reversal=true
and webhook payment.reversed.
Operational detail remains in status_detail (uppercase internal name)
on REST create/list/get. Webhook payment.failed uses error.platform.code
to distinguish posting failures from provider rejections.Valores: pending, processing, confirmed, posted, failed |
payment.amount | object | sí | — |
payment.amount.amount | string | — | — |
payment.amount.currency | string | — | — |
payment.posted_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
payment.account | object | sí | — |
payment.account.type | string | sí | — |
payment.account.value | string | sí | — |
payment.reference | object | sí | — |
payment.reference.client_reference | string | sí | Partner reference sent at payment creation (client_reference), not detail_reference. |
payment.reference.external_reference | string | sí | — |
payment.destination_phone_number | string | — | — |
payment.local_payment | boolean | — | JSON boolean (true/false), not the strings "true"/"false". |
payment.origin_client_name | string | — | — |
provider | object | sí | Shared provider snapshot on REST payment reads and webhook envelopes.
Rail reference is only on payment.external_reference, not duplicated here.
provider_status_* match webhook rejection.reason_code / message / code
when the provider rejected the operation. null when there is no rejection. |
provider.correlation_id | string | sí | Transaction correlation UUID (execution.correlation_id /
metadata.tx.correlationId). Sent to GX/SINPE as correlationId.
Not channel_reference (e.g. MOBILE_APP). Distinct from webhook
event.correlation_id (HTTP request id for this delivery). null when unknown. |
provider.occurred_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
provider.provider_status_code | string | sí | Raw provider Motivo/code (e.g. 31). Same meaning as webhook
rejection.reason_code. null when there is no provider rejection. |
provider.provider_status_desc | string | sí | Provider Motivo text (prefers Detalle). Same meaning as webhook
rejection.message. null when there is no provider rejection. |
provider.provider_status_semantic | string | sí | Normalized platform semantic (e.g. ACCOUNT_BLOCKED). Same meaning as
webhook rejection.code. null when there is no provider rejection. |
payment.succeeded
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
schema_version | string | sí | — |
event | object | sí | — |
event.event_id | string (uuid) | sí | — |
event.event_type | string | sí | —Valores: payment.succeeded |
event.occurred_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
event.correlation_id | string | sí | — |
context | object | sí | — |
context.country_code | string | sí | — |
context.payment_method_code | string | sí | Public payment method catalog. Same values as REST (PIN, SINPE_MOVIL). Internal codes such as PM_PIN are never exposed.Valores: PIN, SINPE_MOVIL |
payment | object | sí | — |
payment.payment_id | string (uuid) | sí | — |
payment.public_id | integer (int64) | sí | — |
payment.status | string | sí | Public payment status catalog. Always lowercase. Same values on REST
(create, list, get) and webhooks (payment.status).
Internal lifecycle states are collapsed:
- pending: initiated, validated, accepted
- processing: processing, pending_processing, posting
- confirmed: confirmed by the rail, not yet ledger-posted
- posted: ledger posted (stays posted after a later reversal)
- failed: failed, rejected, posting_failed, cancelled, expired
A reversal is not a status. Use REST result=reversed / has_reversal=true
and webhook payment.reversed.
Operational detail remains in status_detail (uppercase internal name)
on REST create/list/get. Webhook payment.failed uses error.platform.code
to distinguish posting failures from provider rejections.Valores: pending, processing, confirmed, posted, failed |
payment.amount | object | sí | — |
payment.amount.amount | string | — | — |
payment.amount.currency | string | — | — |
payment.client_reference | string | sí | Partner reference sent at payment creation (client_reference), not detail_reference. |
payment.external_reference | string | sí | — |
payment.fee_total | string | sí | Fee total as decimal string (same currency as amount.currency) |
payment.tax_total | string | sí | Tax total as decimal string (same currency as amount.currency) |
payment.net_amount | string | sí | Net cash impact on the merchant account. PAYIN: amount - fee_total - tax_total (what the merchant receives). PAYOUT: amount + fee_total + tax_total (total debit; fee is charged separately and is not deducted from the transferred amount). |
payment.succeeded_at | string | sí | Timestamp when the provider confirmed the payment succeeded. |
provider | object | sí | Shared provider snapshot on REST payment reads and webhook envelopes.
Rail reference is only on payment.external_reference, not duplicated here.
provider_status_* match webhook rejection.reason_code / message / code
when the provider rejected the operation. null when there is no rejection. |
provider.correlation_id | string | sí | Transaction correlation UUID (execution.correlation_id /
metadata.tx.correlationId). Sent to GX/SINPE as correlationId.
Not channel_reference (e.g. MOBILE_APP). Distinct from webhook
event.correlation_id (HTTP request id for this delivery). null when unknown. |
provider.occurred_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
provider.provider_status_code | string | sí | Raw provider Motivo/code (e.g. 31). Same meaning as webhook
rejection.reason_code. null when there is no provider rejection. |
provider.provider_status_desc | string | sí | Provider Motivo text (prefers Detalle). Same meaning as webhook
rejection.message. null when there is no provider rejection. |
provider.provider_status_semantic | string | sí | Normalized platform semantic (e.g. ACCOUNT_BLOCKED). Same meaning as
webhook rejection.code. null when there is no provider rejection. |
payment.failed
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
schema_version | string | sí | — |
event | object | sí | — |
event.event_id | string (uuid) | sí | — |
event.event_type | string | sí | —Valores: payment.failed |
event.occurred_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
event.correlation_id | string | sí | — |
context | object | sí | — |
context.country_code | string | sí | — |
context.payment_method_code | string | sí | Public payment method catalog. Same values as REST (PIN, SINPE_MOVIL). Internal codes such as PM_PIN are never exposed.Valores: PIN, SINPE_MOVIL |
payment | object | sí | — |
payment.payment_id | string (uuid) | sí | — |
payment.public_id | integer (int64) | sí | — |
payment.status | string | sí | Public payment status catalog. Always lowercase. Same values on REST
(create, list, get) and webhooks (payment.status).
Internal lifecycle states are collapsed:
- pending: initiated, validated, accepted
- processing: processing, pending_processing, posting
- confirmed: confirmed by the rail, not yet ledger-posted
- posted: ledger posted (stays posted after a later reversal)
- failed: failed, rejected, posting_failed, cancelled, expired
A reversal is not a status. Use REST result=reversed / has_reversal=true
and webhook payment.reversed.
Operational detail remains in status_detail (uppercase internal name)
on REST create/list/get. Webhook payment.failed uses error.platform.code
to distinguish posting failures from provider rejections.Valores: pending, processing, confirmed, posted, failed |
payment.amount | object | sí | — |
payment.amount.amount | string | — | — |
payment.amount.currency | string | — | — |
payment.client_reference | string | — | Partner reference sent at payment creation (client_reference), not detail_reference. |
payment.external_reference | string | — | — |
payment.error | object | sí | — |
payment.error.domain | string | sí | —Valores: provider_rejection, platform_validation, platform_posting, platform_infra, platform_reversal, sinpe_rejection |
payment.error.platform | object | sí | — |
provider | object | sí | Shared provider snapshot on REST payment reads and webhook envelopes.
Rail reference is only on payment.external_reference, not duplicated here.
provider_status_* match webhook rejection.reason_code / message / code
when the provider rejected the operation. null when there is no rejection. |
provider.correlation_id | string | sí | Transaction correlation UUID (execution.correlation_id /
metadata.tx.correlationId). Sent to GX/SINPE as correlationId.
Not channel_reference (e.g. MOBILE_APP). Distinct from webhook
event.correlation_id (HTTP request id for this delivery). null when unknown. |
provider.occurred_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
provider.provider_status_code | string | sí | Raw provider Motivo/code (e.g. 31). Same meaning as webhook
rejection.reason_code. null when there is no provider rejection. |
provider.provider_status_desc | string | sí | Provider Motivo text (prefers Detalle). Same meaning as webhook
rejection.message. null when there is no provider rejection. |
provider.provider_status_semantic | string | sí | Normalized platform semantic (e.g. ACCOUNT_BLOCKED). Same meaning as
webhook rejection.code. null when there is no provider rejection. |
rejection | object | — | Rail-agnostic provider rejection. Shared by POST /accounts/validate
(data.rejection) and the payment.failed webhook (rejection).
Object only when the provider rejected the operation; omitted/null otherwise. |
rejection.code | string | sí | Normalized platform semantic derived from reason_code. Only a subset of
reason_code values (see the catalog in the description of POST /accounts/validate
and of the payment.failed webhook contract endpoint) has a distinct entry here —
for any other numeric reason_code, code is SINPE_REJECTED even when message
is specific (e.g. reason_code = 21 "fondos insuficientes" still returns
code = SINPE_REJECTED, it has no dedicated semantic). PHONE_NOT_REGISTERED is
the fallback when reason_code is non-numeric (SINPE_MOVIL wallet flow).Valores: ACCOUNT_CLOSED, ACCOUNT_NOT_FOUND, ACCOUNT_BLOCKED, INVALID_ACCOUNT, IDENT_MISMATCH, IDENT_TYPE_INVALID, INVALID_ID_FORMAT, VALIDATION_ERROR, CURRENCY_MISMATCH, NOT_AUTHORIZED_BY_RECIPIENT, PROFILE_NOT_ALLOWED, CHANNEL_NOT_RECOGNIZED, COMMUNICATION_ERROR, RETRY_LATER, WALLET_PHONE_ORIGIN_INVALID, WALLET_PHONE_ORIGIN_NOT_ENABLED, WALLET_PHONE_DEST_INVALID, WALLET_PHONE_NOT_REGISTERED_BCCR, WALLET_NOT_FOUND, WALLET_PHONE_ALREADY_ACTIVE, PHONE_NOT_REGISTERED, SINPE_REJECTED |
rejection.reason_code | string | sí | Raw Motivo/code returned by SINPE. See the endpoint description of
POST /accounts/validate for the full code catalog (shared by this object
wherever it appears, including the payment.failed webhook). A code not
listed there still produces a response — message falls back to a generic
text and code falls back to SINPE_REJECTED. |
rejection.message | string | sí | Descriptive Motivo text; prefers provider Detalle when present. |
payment.reversed
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
schema_version | string | sí | — |
event | object | sí | — |
event.event_id | string (uuid) | sí | — |
event.event_type | string | sí | —Valores: payment.reversed |
event.occurred_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
event.correlation_id | string | sí | — |
context | object | sí | — |
context.country_code | string | sí | — |
context.payment_method_code | string | sí | Public payment method catalog. Same values as REST (PIN, SINPE_MOVIL). Internal codes such as PM_PIN are never exposed.Valores: PIN, SINPE_MOVIL |
payment | object | sí | — |
payment.payment_id | string (uuid) | sí | — |
payment.public_id | integer (int64) | sí | — |
payment.status | string | sí | Public payment status catalog. Always lowercase. Same values on REST
(create, list, get) and webhooks (payment.status).
Internal lifecycle states are collapsed:
- pending: initiated, validated, accepted
- processing: processing, pending_processing, posting
- confirmed: confirmed by the rail, not yet ledger-posted
- posted: ledger posted (stays posted after a later reversal)
- failed: failed, rejected, posting_failed, cancelled, expired
A reversal is not a status. Use REST result=reversed / has_reversal=true
and webhook payment.reversed.
Operational detail remains in status_detail (uppercase internal name)
on REST create/list/get. Webhook payment.failed uses error.platform.code
to distinguish posting failures from provider rejections.Valores: pending, processing, confirmed, posted, failed |
payment.result | string | sí | —Valores: reversed |
payment.has_reversal | boolean | sí | —Valores: true |
payment.amount | object | sí | — |
payment.amount.amount | string | — | — |
payment.amount.currency | string | — | — |
payment.client_reference | string | — | — |
payment.external_reference | string | — | — |
payment.reversed_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
payment.reason | object | — | — |
payment.reason.code | string | — | — |
payment.reason.message | string | — | — |
provider | object | sí | Shared provider snapshot on REST payment reads and webhook envelopes.
Rail reference is only on payment.external_reference, not duplicated here.
provider_status_* match webhook rejection.reason_code / message / code
when the provider rejected the operation. null when there is no rejection. |
provider.correlation_id | string | sí | Transaction correlation UUID (execution.correlation_id /
metadata.tx.correlationId). Sent to GX/SINPE as correlationId.
Not channel_reference (e.g. MOBILE_APP). Distinct from webhook
event.correlation_id (HTTP request id for this delivery). null when unknown. |
provider.occurred_at | string (date-time) | sí | RFC 3339 timestamp in UTC with second precision and a Z suffix.
Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z. |
provider.provider_status_code | string | sí | Raw provider Motivo/code (e.g. 31). Same meaning as webhook
rejection.reason_code. null when there is no provider rejection. |
provider.provider_status_desc | string | sí | Provider Motivo text (prefers Detalle). Same meaning as webhook
rejection.message. null when there is no provider rejection. |
provider.provider_status_semantic | string | sí | Normalized platform semantic (e.g. ACCOUNT_BLOCKED). Same meaning as
webhook rejection.code. null when there is no provider rejection. |
Última verificación: 2026-09-02 · Responsable: equipo-integraciones