Webhooks
Los webhooks notifican a tu aplicación en tiempo real cuando ocurren eventos — pagos, cambios de suscripción, reembolsos y más. En lugar de consultar el estado de un pago una y otra vez, registrás una URL y Recurrente le hace un POST cada vez que pasa algo.
Consulta el catálogo completo de eventos para ver cuándo se emite cada webhook, su esquema y un payload de ejemplo.
Integrarte toma cuatro pasos: registrás tu endpoint, manejás el evento, verificás la firma y lo probás.
Cuentas conectadas: verificación y FEL
verification.completed: la verificación de la cuenta fue aceptada.verification.rejected: uno o más documentos fueron rechazados; revisadocument_errors.tax_invoice_settings.enabled: FEL terminó de activarse y está listo para emitir. El campotax_invoicingcontiene el estado público, sin credenciales del proveedor.
La plataforma recibe estos eventos con connected: true y el account_id de la cuenta hija. Los eventos de aceptación y activación conservan el estado al ocurrir la transición; para el estado actual de FEL consulta GET /connected_accounts/{id}/tax_invoicing. No se emiten de nuevo por guardar datos sin cambiar el estado. Sus esquemas y ejemplos están en el catálogo de eventos.
Cuentas nuevas y compatibilidad
Las cuentas creadas después de activar este cambio reciben únicamente intent.* cuando existe un reemplazo unificado: tarjeta, transferencia bancaria, cripto, balance y efectivo. Las cuentas existentes conservan ambos formatos, incluso si registran un endpoint nuevo. Los eventos sin reemplazo, como automated_bank_transfer_intent.*, setup_intent.*, subscription.* y refund.*, no cambian; tampoco se eliminan los eventos de creación o actualización que no tienen equivalente unificado.
La regla depende de la cuenta que recibe el webhook: una plataforma existente conserva ambos formatos para sus cuentas conectadas nuevas. Cada Sandbox nuevo usa el formato unificado, aunque su cuenta LIVE sea anterior al cambio.
El payload unificado incluye legacy_id, el ID del mismo cobro en el evento anterior por tipo. Úsalo para vincular registros históricos durante la migración; el ID canónico sigue siendo id.
1. Registra tu endpoint
Un endpoint es una URL pública de tu servidor donde querés recibir las notificaciones (ej. https://tusitio.com/webhooks/recurrente). Las direcciones privadas, locales y de metadata no se aceptan.
Vía Dashboard
Dentro de tu cuenta de Recurrente, ve a Configuración → Desarrolladores y API, haz clic en “Webhooks”, y añade la URL a donde quieres que sean enviados los requests.
Vía API
Podés registrar varias URLs (producción, staging, logging); cada evento se envía a todas.
2. Maneja el evento
Cada webhook entrega un evento en formato JSON con esta estructura. Tu handler ramifica según el campo type para saber qué método de pago lo generó, y según status para saber qué pasó.
El payload tiene tres capas:
Envelope — los campos de identificación del evento:
type— el tipo de pago:payment,bank_transfer,crypto,balance,cash.event_type— el evento:intent.succeeded,intent.failed,intent.pending,intent.canceled,intent.paid.status— el estado:pending,succeeded,failed,canceled,paid. Algunos estados intermedios (p. ej. de tarjeta) se exponen comopending; el valor exacto del procesador siempre está enraw_status.raw_status,id,legacy_id,receipt_number,api_version,created_at.
Datos del pago — amount_in_cents, currency, customer, product, tax_invoice_url, checkout, payment.
details — campos específicos del método de pago. Para payment: comisiones, cuotas, canal, etc. Para bank_transfer: bank_reference (referencia asignada por el banco o la red de pago) y sender_comment (comentario libre que el pagador escribió en la transferencia).
Ejemplo (intent.succeeded, pago con tarjeta):
El envelope y los datos del pago son iguales para todos los tipos; solo cambian type y el contenido de details. Así, un solo handler cubre todos los métodos de pago:
También podés consultar cualquier pago por API con GET /api/intents/{id} — devuelve exactamente el mismo formato que el webhook.
3. Verifica la firma
Cada webhook incluye una firma para que verifiques que viene de Recurrente y no fue alterado. Recomendamos fuertemente verificar las firmas en producción.
Signing Secret
Cada endpoint tiene un signing secret único, con el formato whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw. Hay dos formas de obtenerlo:
- Vía API: el campo
signingSecretse devuelve en la respuesta al crear el endpoint (POST /api/webhook_endpoints). Solo se devuelve en esa respuesta de creación, así que guárdalo de forma segura. - Vía dashboard: lo puedes encontrar en cualquier momento en el dashboard de Svix — accesible desde Configuración → Desarrolladores y API → Webhooks en tu cuenta de Recurrente.
Verificar con Librerías Oficiales (Recomendado)
La forma más fácil de verificar firmas es usando las librerías de verificación de webhooks de Svix, disponibles para muchos lenguajes:
Debes usar el body crudo del request al verificar webhooks. Si parseas el JSON y lo re-serializas, la verificación de firma fallará.
Verificar Manualmente
Cada request de webhook incluye tres headers:
Paso 1. Construye el contenido firmado concatenando el svix-id, svix-timestamp, y el body crudo del request, separados por puntos:
Paso 2. Decodifica la porción en base64 de tu signing secret (todo después de whsec_) y úsalo como llave para un hash HMAC-SHA256 del contenido firmado:
Paso 3. Compara tu firma calculada con la del header svix-signature. El valor del header tiene el prefijo v1, — quita ese prefijo antes de comparar. Usa una comparación de tiempo constante para prevenir ataques de timing.
Paso 4. Opcionalmente, verifica que el svix-timestamp esté dentro de unos minutos del tiempo actual de tu servidor para prevenir ataques de replay.
4. Pruébalo
Un Sandbox envía webhooks sin mover dinero. Pagar un checkout del Sandbox con la tarjeta 4242 4242 4242 4242 envía intent.succeeded (type: payment). Los Sandboxes anteriores al cambio conservan además payment_intent.succeeded. Las entregas van solo a los endpoints registrados con la llave TEST de ese Sandbox.
También puedes abrir el dashboard de webhooks y usar el tab Testing para enviar a tu endpoint un ejemplo de cualquiera de los eventos del catálogo. El mensaje muestra el payload, los intentos y si la entrega fue exitosa. Los pagos de prueba solo simulan tarjeta; para ejercitar el procesamiento real de otros métodos, usa el ambiente correspondiente.
Tipos de eventos
Eventos de pago (intent.*)
Un mismo cobro con balance dispara dos webhooks independientes: intent.succeeded (type: balance) a la cuenta del comercio y intent.paid a la cuenta pagadora.
Eventos de suscripción
Nota: en una suscripción, cuando un pago falla, Recurrente intenta cobrarlo de nuevo 3 y 5 días después. Si ambos re-intentos son fallidos, en ese momento se cancela la suscripción.
Otros eventos
Eventos de movimiento de dinero
Los eventos withdrawal.* y swap.* incluyen una llave transfer con la misma forma unificada que devuelve GET /api/transfers/{id} (los transfer.* ya la usan como forma principal). Si integras con el endpoint unificado, lee siempre esa llave — ver la guía Mover dinero.
Para retiros, lee payload.withdrawal.status (o payload.transfer.status en la forma unificada). withdrawal.update con status: "sent" es éxito terminal para retiros locales: ejecuta tu siguiente paso y no esperes un completed posterior. Si llega completed, trátalo solo como confirmación adicional.
Reintentos
Si tu endpoint no responde con un código 2xx, Recurrente reintentará enviar el webhook:
- Inmediatamente después del primer fallo
- 1 minuto después
- 5 minutos después
- 30 minutos después
- 2 horas después
- 6 horas después
Mejores Prácticas
- Retorna 2xx rápidamente — Confirma la recepción antes de hacer procesamiento pesado
- Maneja duplicados — Los webhooks pueden ser entregados más de una vez; usa el ID del evento para idempotencia
- Verifica la fuente — Valida las firmas de los webhooks como se describe arriba
- Procesa de forma asíncrona — Usa una cola para procesamiento pesado después de confirmar recepción
¿Tu cuenta recibe los eventos por tipo (payment_intent.*, bank_transfer_intent.*, etc.)? Siguen funcionando sin cambios — ver Eventos legacy o la guía para migrar al formato unificado.
Los P2P pueden quedar en in_review. El monto queda reservado hasta la aprobación. Si se rechazan, pasan a cancelled y el monto vuelve al remitente.

