Ir al contenido

Webhook de suscripción

Ver .md

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.

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.

{
"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
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ó

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.

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
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
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
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

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
{
"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"
}
}

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.

{
"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"
}
}
{
"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.

{
"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."
}
}
{
"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."
}
}

Última actualización: