Operación · 10
Webhooks
Cuando una verificación queda emitida, Revísame envía un POST firmado a la webhook_url que declaraste al crearla.
Solo hay webhook para
verificacion.emitida. Una verificación que termina en no_concluyente no genera webhook: detéctala con GET /v1/verificaciones/{verificacion_id} (ver Ciclo de vida).La petición
POST /revisame/eventos HTTP/1.1
Content-Type: application/json
Revisame-Event: verificacion.emitida
Revisame-Event-Id: <id estable del evento>
Revisame-Signature: t=1790000000,v1=5f2c…(64 hex)
{"cobertura_fuentes":{…},"cobro":{"estatus":"cargada","modo":"consumo","monto":350.0,"moneda":"MXN"},"estado":"emitida","recomendacion":{…},"solicitud_id":"…","verificacion_id":"…"}
El cuerpo trae la verificación emitida (identificadores, estado, recomendación, cobertura de fuentes y el bloque cobro, idéntico al que devuelve GET). Nunca trae la CURP ni el reporte crudo.
Verifica la firma
- Lee el cuerpo crudo tal como llegó (antes de parsear el JSON).
- De
Revisame-Signaturetomat(segundos unix) yv1. - Calcula
HMAC_SHA256(secreto, "<t>.<cuerpo_crudo>")en hexadecimal y compáralo conv1en tiempo constante. - Rechaza si
tdifiere de tu reloj en más de 300 segundos.
El secreto es por socio, distinto de tu llave de API, y te lo entrega Revísame por un canal seguro.
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
export function firmaValida(cuerpoCrudo, cabecera, secreto, ahora = Date.now() / 1000) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(cabecera ?? "");
if (!m) return false;
const t = Number(m[1]);
if (Math.abs(ahora - t) > 300) return false;
const esperado = createHmac("sha256", secreto).update(`${t}.${cuerpoCrudo}`).digest();
const recibido = Buffer.from(m[2], "hex");
return recibido.length === esperado.length && timingSafeEqual(recibido, esperado);
}
// Express: usa el cuerpo crudo
app.post("/revisame/eventos", express.raw({ type: "application/json" }), (req, res) => {
if (!firmaValida(req.body.toString("utf8"), req.get("Revisame-Signature"), process.env.REVISAME_WEBHOOK_SECRETO)) {
return res.sendStatus(400);
}
res.sendStatus(204); // responde rápido…
encolar(JSON.parse(req.body)); // …y procesa después
});
Python
import hmac, hashlib, re, time
def firma_valida(cuerpo_crudo: bytes, cabecera: str, secreto: str) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", cabecera or "")
if not m:
return False
t = int(m.group(1))
if abs(time.time() - t) > 300:
return False
esperado = hmac.new(secreto.encode(), f"{t}.".encode() + cuerpo_crudo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, m.group(2))
Entrega y reintentos
- Responde 2xx en menos de 10 segundos. Procesa el evento de forma asíncrona.
- Si no respondes 2xx, Revísame reintenta: hasta 6 intentos en total, el primero inmediato y luego a los 30 s, 2 min, 10 min, 1 h y 6 h.
- Deduplica por
Revisame-Event-Id: es estable para el mismo evento, así que un reintento trae el mismo valor. - Los eventos pueden llegar fuera de orden o repetidos; trata el webhook como aviso y, si dudas, confirma con
GET /v1/verificaciones/{verificacion_id}.
Tu webhook_url
- Debe ser
httpsy pública. - Usa una ruta neutra (p. ej.
/revisame/eventos). Rutas o parámetros que describen a la persona hacen que la petición se rechace conpeticion_invalida. - No incluyas secretos en la URL; la autenticidad la da la firma.