Webhooks
API v0.1.0En esta página
Rutiva envía notificaciones HTTP a tu servidor cuando cambia el estado de un payment_intent. Esencial para reconciliación, cumplimiento de pedidos y notificaciones al usuario.
Qué son
Eventos firmados con HMAC-SHA256 (estilo Stripe) sobre el body raw + timestamp. Permite verificar autoría sin compartir secretos por el canal.
Eventos emitidos
payment_intent.createdpayment_intent.succeededpayment_intent.failedpayment_intent.canceled
Registrar tu endpoint receptor
POST /v1/webhook_endpoints (requiere sk_):
json
{
"url": "https://tu-dominio.com/webhooks/rutiva",
"enabled_events": ["payment_intent.succeeded", "payment_intent.failed"]
}Filtros soportados en enabled_events:
["*"]— todos los eventos.["payment_intent.succeeded"]— exacto.["payment_intent.*"]— wildcard por prefijo.
Response 201 (UNA SOLA VEZ):
json
{
"external_id": "whe_xxx",
"url": "https://tu-dominio.com/webhooks/rutiva",
"signing_secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx"
}Headers entrantes
bash
Content-Type: application/json
X-Rutiva-Signature: t=1715539200,v1=<sha256_hex>
X-Rutiva-Event-Type: payment_intent.succeededVerificación de firma HMAC
Algoritmo:
bash
v1 = HMAC_SHA256(signing_secret, f"{t}.{body}")Tu handler debe:
- Leer el body raw (sin parsear JSON antes — la firma es sobre los bytes exactos).
- Recalcular HMAC con tu
signing_secretguardado. - Comparar con
v1usando comparación timing-safe (hmac.compare_digest/crypto.timingSafeEqual). - Rechazar si
tes muy viejo (> 5 min) para mitigar replays. - Responder
2xxrápido (< 10s). Procesar el evento idempotentemente.
Ejemplos de handler
javascript
import crypto from "crypto";
import express from "express";
const app = express();
app.post(
"/webhooks/rutiva",
express.raw({ type: "application/json" }),
(req, res) => {
const header = req.header("X-Rutiva-Signature");
const [tPart, v1Part] = header.split(",");
const t = tPart.split("=")[1];
const v1 = v1Part.split("=")[1];
const body = req.body.toString();
const expected = crypto
.createHmac("sha256", process.env.RUTIVA_WEBHOOK_SECRET)
.update(`${t}.${body}`)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) {
return res.status(400).send("invalid_signature");
}
if (Math.abs(Date.now() / 1000 - parseInt(t, 10)) > 300) {
return res.status(400).send("timestamp_out_of_tolerance");
}
const event = JSON.parse(body);
// procesar event.type, event.data ...
res.status(200).send("ok");
}
);Notas
- TLS obligatorio en la URL receptora (
https://). - Si tu endpoint falla (no responde 2xx), por ahora no se reintenta en MVP. Reintentos durables vienen en próxima versión.
- El mismo evento puede llegar más de una vez en el futuro (cuando se habiliten retries). Procesa idempotentemente.