Webhook de suscripción
Qué te avisamos
Sección titulada «Qué te avisamos»Una suscripción cobra sola, mes tras mes, fuera de cualquier petición tuya. El webhook es cómo te contamos qué pasó: que se renovó, que el cobro falló, que el cliente la canceló, que dejó de dar servicio, que la prueba está por terminar.
Configura la URL en el campo webhook_url al
crear la suscripción, o en
advanced_options.result_urls.webhook del plan.
Cómo se entrega y cómo se verifica
Sección titulada «Cómo se entrega y cómo se verifica»Igual que todos nuestros webhooks: POST con Content-Type: application/json, header
Signature con el HMAC-SHA256 del cuerpo crudo, 3 segundos de espera y hasta 7 intentos
repartidos en unas 17 horas. Si tu endpoint estuvo caído más tiempo, el evento sigue en el
historial de webhooks durante 30 días y puedes reenviarlo con el
mismo meta.event_id.
El detalle completo, con ejemplos de verificación en PHP, Node y Python, está en Webhooks. Aquí solo va lo específico de suscripciones.
Forma del cuerpo
Sección titulada «Forma del cuerpo»{ "meta": { "event_id": "…", "event_type": "…", "timestamp": "…", "api_version": "v1" }, "subscription": { "…": "estado completo de la suscripción" }, "status": { "key": "renew", "value": "Renovado", "description": "…" }, "transaction": { "…": "solo cuando hubo cobro" }}| Bloque | Siempre viene | Qué es |
|---|---|---|
meta |
Sí | Identificador de la entrega. Ver Identificar cada entrega |
subscription |
Sí | El estado resultante de la suscripción, ya aplicado el evento |
status |
Sí | El evento: qué acaba de pasar |
transaction |
No | Solo en los eventos que implican un cobro |
previous_plan_id |
No | Solo en plan_changed: el plan que tenía antes |
Catálogo de eventos
Sección titulada «Catálogo de eventos»status.key |
meta.event_type |
Trae transaction |
|---|---|---|
new |
subscription.created |
Sí |
renew |
subscription.renewed |
Sí |
retry |
subscription.payment_retry |
Sí |
finished |
subscription.finished |
Sí |
finished_by_limit |
subscription.finished_by_limit |
No |
canceled |
subscription.canceled |
No |
ended |
subscription.ended |
No |
paused |
subscription.paused |
No |
resumed |
subscription.resumed |
No |
plan_changed |
subscription.plan_changed |
Depende del flujo |
trial_will_end |
subscription.trial_will_end |
No |
renewed |
subscription.invitation_accepted |
Sí, si el cobro se ejecutó |
Cuándo llega cada uno
Sección titulada «Cuándo llega cada uno»Las dudas que más nos llegan, respondidas:
¿Qué evento marca el final por falta de pago?
finished. La secuencia completa de un cobro que falla es: retry en cada intento
mientras la suscripción siga viva, y finished cuando se agota la ventana de gracia sin
haber cobrado. No hay un evento aparte para “se acabó la gracia”.
¿retry llega una vez o en cada intento?
En cada intento de cobro fallido. La cadencia de reintentos la define el plan
(ventana de gracia) y el dunning del comercio.
¿Siempre se reintenta mientras quede gracia?
No. La causal del rechazo manda sobre la ventana de gracia: un rechazo que no puede
aprobar en otro intento corta el dunning de inmediato y el evento que llega es
finished, no retry, aunque grace_ends_at siga en el futuro. Ver
Rechazos que no se reintentan.
¿En qué se diferencian renew, renewed y plan_changed?
| Quién lo dispara | Qué pasó | |
|---|---|---|
renew |
Nosotros, en el cobro automático | Se cobró el ciclo y la suscripción sigue |
renewed |
El suscriptor | Aceptó una invitación de renovación o reactivación que le llegó por correo |
plan_changed |
El comercio o el suscriptor | Se cambió de plan, directo con prorrateo o aceptando la invitación |
¿paused y resumed pueden originarse de nuestro lado?
No. Solo los disparan POST /subscription/{id}/pause y /resume, que llamas tú o alguien
desde el panel del comercio. Efipay no pausa suscripciones por su cuenta.
Después de cancelar, ¿puede llegarme un renew?
No. El motor de cobro excluye toda suscripción con canceled_at, así que en cuanto la
cancelación queda registrada no hay más cobros ni más renew. Lo que sí sigue vigente
hasta cancel_at es el servicio, salvo que canceles con
at_period_end: false.
¿Cómo sé cuándo cortar el acceso de una suscripción cancelada?
Con ended (subscription.ended). Al cancelar al final del período recibes canceled,
pero la suscripción sigue activa (o en_gracia) con cancel_at_period_end: true.
Cuando deja de dar servicio —al llegar ends_at o, si hay gracia posterior, el fin de
la gracia—, un proceso que corre cada hora envía ended, una sola vez por
suscripción. En ese momento subscription.status es cancelada,
cancel_at_period_end es false e is_active es false. También llega tras una
cancelación inmediata (at_period_end: false) y tras finished_by_limit (límite de cobros
alcanzado). Como Stripe con customer.subscription.deleted.
Sobre los identificadores
Sección titulada «Sobre los identificadores»Es la confusión más común, porque conviven dos tipos:
| Campo | Tipo | Ejemplo |
|---|---|---|
subscription.id |
UUID | 9ad70d0e-61e1-4c17-99c3-355b50d79954 |
subscription.plan.id |
UUID | 9ae4dc2d-8b2e-4920-ac1f-80f2d648a2e6 |
subscription.subscriber.id |
UUID | 9ae92bbb-2cab-4569-85b1-6173d0fb9d4b |
transaction.transaction_id |
Entero | 108 |
office.id, commerce_id |
Entero | 1 |
Detalle de los objetos
Sección titulada «Detalle de los objetos»subscription
Sección titulada «subscription»| Campo | Descripción |
|---|---|
id |
UUID de la suscripción |
status |
Estado actual: trial, activa, en_gracia, pausada, cancelada, finalizada |
starts_at |
Inicio del período vigente |
ends_at |
Fin del período vigente |
grace_ends_at |
Hasta cuándo se puede reintentar el cobro sin cortar el servicio |
on_grace_period |
true si está dentro de la ventana de gracia ahora mismo |
trial_ends_at |
Fin del período de prueba, si lo hay |
ends_at_iso |
El mismo ends_at en ISO 8601 con offset (2026-10-07T00:00:00-05:00) |
canceled_at |
Cuándo se canceló. null si no está cancelada |
cancel_at_period_end |
true si está cancelada pero todavía da servicio hasta cancel_at |
cancel_at |
ISO 8601 con offset: cuándo deja de dar servicio una suscripción cancelada (ends_at, o el fin de la gracia si es posterior). null si no está cancelada |
paused_at / resumes_at |
Pausa de cobros y cuándo se reanuda (null = indefinida) |
is_active / is_inactive |
Si sigue dando servicio |
is_ended / is_expired |
Si terminó el período / si terminó sin gracia disponible |
on_trial / on_discount |
Si está en prueba / con descuento vigente |
production |
false en pruebas |
description |
Tu descripción de la suscripción |
metadata |
Tus pares clave-valor. Ver Metadata |
webhook_url |
La URL a la que te estamos escribiendo |
plan |
Objeto del plan, con su metadata |
subscriber |
Objeto del suscriptor, con su metadata |
next_charge |
Próximo cobro programado |
office |
Sede |
| Campo | Descripción |
|---|---|
name |
Nombre del plan |
description |
Descripción |
price |
Precio del ciclo |
currency_type |
Moneda |
invoice_period / invoice_interval |
Cada cuánto se cobra |
trial_period / trial_interval |
Duración de la prueba |
grace_period / grace_interval |
Duración de la gracia |
metadata |
Tus pares clave-valor del plan |
subscriber
Sección titulada «subscriber»| Campo | Descripción |
|---|---|
identification_type |
Tipo de documento |
id_number |
Número de documento |
name / last_name |
Nombre y apellido |
email |
Correo |
phone_code / cellphone_number |
Indicativo y celular |
billing_address / billing_city / billing_country |
Datos de facturación |
metadata |
Tus pares clave-valor del suscriptor |
next_charge
Sección titulada «next_charge»| Campo | Descripción |
|---|---|
amount |
Monto del próximo cobro, con el prorrateo ya sumado si hubo un cambio de plan con create_prorations |
currency_type |
Moneda |
next_renew_at |
Día del próximo cobro, Y-m-d en Colombia. Mismo valor que next_transaction y que next-transaction.next_renew_at (antes llegaba como medianoche UTC) |
charge_scheduled_at |
ISO 8601 con offset: cuándo corre el proceso diario que lo cobra, las 19:00 de Colombia de ese día. El renew confirma el cobro |
proration_amount |
Parte del monto que viene de un prorrateo. null o 0 si no hay |
transaction
Sección titulada «transaction»Es el mismo objeto que devuelve el
checkout por API: incluye
transaction_id, amount, status, status_key, response_code, error, card y
transaction_details. En un rechazo (retry, finished), error es
{code, message, retryable, action}; ver Códigos de error.
| Campo | Descripción |
|---|---|
key |
Identificador heredado del evento (ver catálogo) |
value |
Nombre corto para mostrar (Renovado, Reintento de Pago…) |
description |
Frase explicativa |
Ejemplos por evento
Sección titulada «Ejemplos por evento»renew — el cobro recurrente salió bien
Sección titulada «renew — el cobro recurrente salió bien»{ "meta": { "event_id": "9b1f0e34-6b2a-4a7e-9d0a-3c5f2b7a1d80", "event_type": "subscription.renewed", "timestamp": "2026-09-07T10:32:16-05:00", "api_version": "v1" }, "subscription": { "id": "9ad70d0e-61e1-4c17-99c3-355b50d79954", "status": "activa", "starts_at": "2026-09-07 00:00:00", "ends_at": "2026-10-07 00:00:00", "ends_at_iso": "2026-10-07T00:00:00-05:00", "grace_ends_at": "2026-10-10 00:00:00", "on_grace_period": false, "trial_ends_at": null, "canceled_at": null, "cancel_at_period_end": false, "cancel_at": null, "paused_at": null, "resumes_at": null, "is_active": true, "is_inactive": false, "on_trial": false, "on_discount": false, "canceled": false, "is_ended": false, "is_expired": false, "production": true, "description": null, "metadata": { "user_id": "42" }, "webhook_url": "https://tu-sistema.com/webhooks/efipay", "plan": { "id": "9ae4dc2d-8b2e-4920-ac1f-80f2d648a2e6", "name": "Plan mensual", "price": 49900, "currency_type": "COP", "invoice_period": 1, "invoice_interval": "month", "metadata": null }, "subscriber": { "id": "9ae92bbb-2cab-4569-85b1-6173d0fb9d4b", "identification_type": "CC", "id_number": "1020304050", "name": "Ana", "last_name": "Rojas", "email": "ana@correo.com", "metadata": { "crm_id": "C-881" } }, "next_charge": { "amount": 49900, "currency_type": "COP", "next_renew_at": "2026-10-07", "charge_scheduled_at": "2026-10-07T19:00:00-05:00", "proration_amount": null }, "office": { "id": 1, "name": "Principal" } }, "transaction": { "transaction_id": 90210, "amount": "49900.00", "currency_type": "COP", "payment_method": "credit", "payment_method_source": "Visa", "status": "Aprobada", "status_key": "approved", "response_code": "00", "error": null, "card": { "franchise": "Visa", "bin": "453210", "last_four": "7890" }, "approved_at": "2026-09-07 10:32:16" }, "status": { "key": "renew", "value": "Renovado", "description": "El pago ha sido exitoso, la suscripción fue renovada y seguirá activa" }}Rechazos que no se reintentan
Sección titulada «Rechazos que no se reintentan»Reintentar un cobro sobre una tarjeta que el emisor mandó retener, que está reportada como robada o que venció no puede aprobar nunca: solo suma rechazos y castiga la tasa de aprobación del comercio. Por eso el reintento depende de la causal:
| Causal del rechazo | Códigos | Qué pasa |
|---|---|---|
| Transitoria (fondos, límites, emisor no disponible, timeout) | 51, 61, 65, 68, 90, 91, 93, 96, 05, 92, … |
Se reintenta dentro de la ventana de gracia → retry |
| Tarjeta inservible (retenida, robada, restringida, vencida, bloqueada) | 04, 07, 14, 33, 34, 35, 36, 41, 43, 46, 54, 62, 70, 83, 5C, 9G |
Se corta el dunning y además la tarjeta guardada queda bloqueada → finished |
| Transacción o dato inválido | 03, 12, 13, 16, 57, 58, 63, 76–81, 98 |
Se corta el dunning → finished |
05 y 92 se reintentan a propósito: el emisor los usa tanto para una tarjeta bloqueada
como para un timeout de red, y cortar un cobro por un timeout sería peor que reintentarlo.
Cuando la tarjeta guardada queda bloqueada, el comercio la ve marcada en el listado de suscriptores y puede desbloquearla desde ahí si el tarjetahabiente resuelve el problema con su banco.
retry — el cobro falló y se reintentará
Sección titulada «retry — el cobro falló y se reintentará»{ "meta": { "event_id": "…", "event_type": "subscription.payment_retry", "timestamp": "…", "api_version": "v1" }, "subscription": { "id": "9ad70d0e-61e1-4c17-99c3-355b50d79954", "status": "en_gracia", "ends_at": "2026-09-07 00:00:00", "grace_ends_at": "2026-09-10 00:00:00", "on_grace_period": true, "is_active": true }, "transaction": { "transaction_id": 90211, "status": "Rechazada", "status_key": "rejected", "response_code": "51", "error": { "code": "51", "message": "Transacción declinada. Fondos insuficientes", "retryable": false, "action": "contact_issuer" } }, "status": { "key": "retry", "value": "Reintento de Pago", "description": "El pago no fue exitoso, se volverá a intentar el pago, la suscripción sigue activa" }}finished — se agotó la gracia sin cobrar
Sección titulada «finished — se agotó la gracia sin cobrar»{ "meta": { "event_id": "…", "event_type": "subscription.finished", "timestamp": "…", "api_version": "v1" }, "subscription": { "id": "9ad70d0e-61e1-4c17-99c3-355b50d79954", "status": "finalizada", "on_grace_period": false, "is_active": false, "is_expired": true }, "transaction": { "transaction_id": 90215, "status": "Rechazada", "status_key": "rejected" }, "status": { "key": "finished", "value": "Suscripción Finalizada", "description": "La suscripción no pudo ser renovada, la suscripción esta inactiva, no se seguirán haciendo cobros" }}canceled — sin cobro, sin objeto transaction
Sección titulada «canceled — sin cobro, sin objeto transaction»{ "meta": { "event_id": "…", "event_type": "subscription.canceled", "timestamp": "…", "api_version": "v1" }, "subscription": { "id": "9ad70d0e-61e1-4c17-99c3-355b50d79954", "status": "activa", "canceled": true, "canceled_at": "2026-09-07 11:04:00", "ends_at": "2026-10-07 00:00:00", "cancel_at_period_end": true, "cancel_at": "2026-10-07T00:00:00-05:00", "is_active": true }, "status": { "key": "canceled", "value": "Suscripción Cancelada", "description": "La suscripción ha sido cancelada por el comercio, permanecerá activa hasta el fin del período actual" }}ended — la suscripción cancelada dejó de dar servicio
Sección titulada «ended — la suscripción cancelada dejó de dar servicio»{ "meta": { "event_id": "…", "event_type": "subscription.ended", "timestamp": "…", "api_version": "v1" }, "subscription": { "id": "9ad70d0e-61e1-4c17-99c3-355b50d79954", "status": "cancelada", "canceled": true, "canceled_at": "2026-09-07 11:04:00", "ends_at": "2026-10-07 00:00:00", "cancel_at_period_end": false, "cancel_at": "2026-10-07T00:00:00-05:00", "is_active": false }, "status": { "key": "ended", "value": "Suscripción Finalizada", "description": "La suscripción cancelada llegó al fin de su período y dejó de dar servicio." }}Llega una vez por suscripción, en la primera pasada del proceso horario después de
cancel_at. Es el momento de cortar el acceso.
paused y resumed
Sección titulada «paused y resumed»{ "meta": { "event_id": "…", "event_type": "subscription.paused", "timestamp": "…", "api_version": "v1" }, "subscription": { "id": "9ad70d0e-61e1-4c17-99c3-355b50d79954", "status": "pausada", "paused_at": "2026-09-07 11:20:00", "resumes_at": "2026-12-01 00:00:00" }, "status": { "key": "paused", "value": "Suscripción Pausada", "description": "Los cobros de la suscripción han sido pausados." }}plan_changed — con el plan anterior
Sección titulada «plan_changed — con el plan anterior»{ "meta": { "event_id": "…", "event_type": "subscription.plan_changed", "timestamp": "…", "api_version": "v1" }, "subscription": { "id": "9ad70d0e-61e1-4c17-99c3-355b50d79954", "status": "activa", "plan": { "id": "9b0100aa-1111-2222-3333-444455556666", "name": "Plan anual", "price": 499000 } }, "previous_plan_id": "9ae4dc2d-8b2e-4920-ac1f-80f2d648a2e6", "status": { "key": "plan_changed", "value": "Plan Cambiado", "description": "El plan de la suscripción fue cambiado con prorrateo." }}trial_will_end — aviso previo al primer cobro
Sección titulada «trial_will_end — aviso previo al primer cobro»{ "meta": { "event_id": "…", "event_type": "subscription.trial_will_end", "timestamp": "…", "api_version": "v1" }, "subscription": { "id": "9ad70d0e-61e1-4c17-99c3-355b50d79954", "status": "trial", "on_trial": true, "trial_ends_at": "2026-09-10 00:00:00", "next_charge": { "amount": 49900, "currency_type": "COP", "next_renew_at": "2026-09-10" } }, "status": { "key": "trial_will_end", "value": "Prueba por finalizar", "description": "El período de prueba de la suscripción está por finalizar." }}