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 -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"]
}'{
"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=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdDonde 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.createdsubscription.canceledcharge.approvedcharge.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.