Ciclo de vida de contracargos

Un contracargo se abre cuando el tarjetahabiente o el banco emisor disputa una transacción de tarjeta pagada. Recurrente vincula el contracargo a la factura original, descuenta el monto disputado del balance de Recurrente, solicita documentación de soporte y registra la resolución del banco.

Los webhooks de contracargos no son eventos unificados intent.*. Usan sus propios eventos: dispute.create y dispute.update.

Ciclo típico

  1. El emisor reporta el contracargo. Recurrente recibe la alerta del emisor o la crea desde un correo del emisor cuando logra encontrar la transacción.
  2. Se abre el contracargo. El estado normalmente es needs_response. Recurrente debita el monto disputado del balance, envía un correo al propietario de la cuenta y envía un webhook dispute.create.
  3. Envías documentación. Recurrente revisa los documentos de soporte y puede mover el contracargo a under_review. Esto envía un webhook dispute.update.
  4. El emisor revisa el caso. El contracargo sigue en progreso mientras el emisor decide. Si no llega documentación a tiempo, Recurrente puede marcar el contracargo como closed.
  5. El contracargo se resuelve. Recurrente actualiza el estado a won, lost u otro estado operativo final. Un contracargo won acredita el monto disputado de vuelta al balance. Un contracargo lost o closed deja el débito original en firme.

No todos los contracargos pasan por todos los pasos. Por ejemplo, un contracargo puede cerrarse por falta de respuesta, resolverse directamente por el emisor o registrarse como charge_refunded cuando el cargo fue reembolsado durante el manejo del caso.

Estados

EstadoSignificado
needs_responseRecurrente necesita documentación de soporte del comercio. Es el estado inicial normal.
under_reviewRecurrente recibió documentación o el caso está en revisión por Recurrente, el adquirente o el emisor.
charge_refundedEl cargo original fue reembolsado durante el manejo del contracargo.
closedEl caso se cerró sin una representación exitosa, comúnmente porque no se recibió documentación a tiempo.
wonEl contracargo se resolvió a favor del comercio. Recurrente acredita el monto disputado de vuelta al balance.
lostEl contracargo se resolvió en contra del comercio. El débito original al balance queda en firme.

Webhooks

EventoCuándo se envía
dispute.createSe crea un contracargo. El payload incluye el status y reason iniciales.
dispute.updateSe actualiza un contracargo. Esto incluye cambios de estado como under_review, closed, won y lost.

dispute.update también puede enviarse por cambios guardados que no son cambios de estado, así que lee siempre el campo status en vez de asumir que cada update avanzó el ciclo.

Payload

1{
2 "id": "di_ab12cd34",
3 "api_version": "2024-04-24",
4 "created_at": "2026-06-17T18:00:00Z",
5 "status": "needs_response",
6 "reason": "fraudulent",
7 "event_type": "dispute.create"
8}
CampoDescripción
idID estable del contracargo. Se mantiene igual en futuros eventos dispute.update del mismo contracargo.
api_versionVersión del payload webhook.
created_atFecha en que se creó el contracargo en Recurrente.
statusEstado actual del contracargo. Úsalo como fuente de verdad para manejar el ciclo de vida.
reasonRazón reportada por el emisor o tarjetahabiente.
event_typedispute.create o dispute.update.

Razones

El campo reason puede ser uno de:

bank_cannot_process, check_returned, credit_not_processed, customer_initiated, debit_not_authorized, duplicate, fraudulent, general, incorrect_account_details, incorrect_amount, insufficient_funds, product_not_received, product_unacceptable, subscription_canceled, unrecognized.

Cuentas conectadas

Si el contracargo pertenece a una cuenta hija conectada y tu cuenta plataforma tiene webhooks activos, tu plataforma puede recibir los mismos eventos con estos campos adicionales:

1{
2 "id": "di_ab12cd34",
3 "api_version": "2024-04-24",
4 "created_at": "2026-06-17T18:00:00Z",
5 "status": "won",
6 "reason": "fraudulent",
7 "event_type": "dispute.update",
8 "connected": true,
9 "account_id": "ac_child123"
10}
  • connected: true indica que el evento fue generado por una cuenta conectada.
  • account_id es la cuenta que generó el evento.

Patrón para tu handler

1app.post("/webhooks/recurrente", (req, res) => {
2 const event = req.body
3
4 if (event.event_type === "dispute.create" || event.event_type === "dispute.update") {
5 upsertDispute({
6 id: event.id,
7 status: event.status,
8 reason: event.reason,
9 connectedAccountId: event.account_id
10 })
11 }
12
13 res.sendStatus(200)
14})

Usa el header svix-id para idempotencia de entrega, y trata event.id como el ID del objeto de contracargo.