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 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.
{warning} Si el plan tiene
result_urls.webhook, esa URL gana sobre elwebhook_urlque mandes al crear la suscripción.
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.
El detalle completo, con ejemplos de verificación en PHP, Node y Python, está en Webhooks. Aquí solo va lo específico de suscripciones.
{danger} Verifica la firma antes de leer el cuerpo. Sin eso, cualquiera que conozca tu URL puede decirte que se renovó una suscripción que nunca se cobró.
{
"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 |
{success} No necesitas una consulta extra.
subscriptionya traestatus,ends_at,grace_ends_at,on_grace_period,is_active,paused_at,resumes_atynext_charge. Con eso decides sin volver a llamarnos.
{info} No confundas los dos
status. El de primer nivel describe el evento (renew,canceled…). El de dentro desubscriptiones el estado de la suscripción (trial,activa,en_gracia,pausada,cancelada,finalizada); ver Estados.
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 time |
subscription.finished_by_limit |
No |
canceled |
subscription.canceled |
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ó |
{danger}
finished timelleva un espacio literal. No es una errata: es el valor real y no lo vamos a cambiar, porque hay integraciones que ya lo manejan. Usameta.event_typesi prefieres una clave sin sorpresas.
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.
¿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 ends_at es el servicio, salvo que canceles con
at_period_end: false.
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 |
{info} Las suscripciones, planes, suscriptores y grupos han sido UUID desde el principio. No existen suscripciones antiguas con id numérico que haya que migrar. Lo numérico es el consecutivo de la transacción y los ids de sede y comercio.
{warning} Guárdalos todos como cadena, también los numéricos. Un entero de transacción puede crecer más allá de lo que tu lenguaje maneja con seguridad, y un UUID parseado como número se corrompe en silencio.
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 |
canceled_at |
Cuándo se canceló. null si sigue vigente |
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 |
webhook_url |
La URL a la que te estamos escribiendo |
plan |
Objeto del plan |
subscriber |
Objeto del suscriptor |
next_charge |
Próximo cobro programado |
office |
Sede |
plan| 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 |
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 |
next_charge| Campo | Descripción |
|---|---|
amount |
Monto del próximo cobro |
currency_type |
Moneda |
next_renew_at |
Fecha del próximo cobro |
transactionEs el mismo objeto que devuelve el
checkout por API: incluye
transaction_id, amount, status, status_key, response_code, error, card y
transaction_details.
status| Campo | Descripción |
|---|---|
key |
Identificador heredado del evento (ver catálogo) |
value |
Nombre corto para mostrar (Renovado, Reintento de Pago…) |
description |
Frase explicativa |
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",
"grace_ends_at": "2026-10-10 00:00:00",
"on_grace_period": false,
"trial_ends_at": null,
"canceled_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,
"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"
},
"subscriber": {
"id": "9ae92bbb-2cab-4569-85b1-6173d0fb9d4b",
"identification_type": "CC",
"id_number": "1020304050",
"name": "Ana",
"last_name": "Rojas",
"email": "ana@correo.com"
},
"next_charge": {
"amount": 49900,
"currency_type": "COP",
"next_renew_at": "2026-10-07"
},
"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"
}
}
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": "Fondos insuficientes en la cuenta del tarjetahabiente.",
"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"
}
}
{warning}
on_grace_period: trueconis_active: truesignifica sigue dando servicio, aunque el cobro haya fallado. No cortes el acceso todavía: espera afinished.
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{
"meta": { "event_id": "…", "event_type": "subscription.canceled", "timestamp": "…", "api_version": "v1" },
"subscription": {
"id": "9ad70d0e-61e1-4c17-99c3-355b50d79954",
"status": "cancelada",
"canceled_at": "2026-09-07 11:04:00",
"ends_at": "2026-10-07 00:00: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"
}
}
{info} Fíjate en
is_active: trueconcanceled_atya puesto: está cancelada pero el cliente conserva el servicio hastaends_at. Conat_period_end: false,ends_atsería igual acanceled_atyis_activevendría enfalse.
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{
"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{
"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."
}
}