GuíasWebhooks

Webhooks

Hay dos flujos de webhooks distintos en Rekurra: los que Rekurra te envía a ti (para que reacciones a cambios en suscripciones y cobros), y el ingress de Wompi hacia Rekurra (interno, informativo para entender el pipeline completo).

Webhooks que Rekurra te envía

1. Registra un endpoint

curl
curl -X POST https://api.rekurra.dev/v1/webhook-endpoints \
  -H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tuapp.com/webhooks/rekurra",
    "events": ["subscription.created", "charge.approved", "charge.declined", "subscription.canceled"]
  }'
201 Created
{
  "id": "we_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "url": "https://tuapp.com/webhooks/rekurra",
  "secret": "whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "events": ["subscription.created", "charge.approved", "charge.declined", "subscription.canceled"],
  "active": true
}
⚠️

El secret se muestra una sola vez, en la respuesta de creación. Guárdalo de forma segura — lo necesitas para verificar la firma de cada entrega.

2. Verifica la firma Rekurra-Signature

Cada entrega incluye el header:

Rekurra-Signature: t=1735142400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

Donde v1 = hmac_sha256(secret, t + "." + body) sobre el cuerpo crudo de la petición.

const crypto = require("crypto");
 
function verificarFirma(rawBody, header, secret) {
  const [tPart, v1Part] = header.split(",");
  const t = tPart.split("=")[1];
  const v1 = v1Part.split("=")[1];
 
  const payload = `${t}.${rawBody}`;
  const esperado = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
 
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperado));
}

3. Eventos disponibles

Algunos de los eventos que puedes suscribir:

  • subscription.created
  • subscription.canceled
  • charge.approved
  • charge.declined

Todos los eventos históricos quedan también disponibles vía API → Events para auditoría o reprocesamiento.

Reintentos

Si tu endpoint no responde 200 (o hace timeout), Rekurra reintenta la entrega con backoff exponencial vía una cola de salida (outbox). Responde 200 tan pronto verifiques la firma — procesa el efecto de forma asíncrona si es una operación larga.

Ingress: cómo Wompi le habla a Rekurra

Este flujo es interno (no lo implementas tú), pero entenderlo ayuda a saber por qué tus webhooks a veces llegan segundos después del cobro:

POST /webhooks/wompi/{developerId} recibe el evento crudo de Wompi (sin Bearer token), lo autentica verificando signature.checksum con tu EventsSecret en tiempo constante, lo persiste (deduplicado por checksum único) y, en transaction.updated, empareja el ChargeAttempt correspondiente por wompi_transaction_id o reference. Cuando la transacción queda APPROVED, Rekurra calcula y descuenta el fee (ver Saldo prepago) y entonces dispara tu webhook charge.approved.

Responde: 200 si todo bien, 401 si la firma no es válida, 500 para forzar un reintento de Wompi, 413 si el cuerpo excede 64 KB.