Webhooks
Recibe eventos en tu sistema, HMAC + retries.
Cuando algo pasa en Replai — un mensaje entra, una sesión se conecta, un envío falla — firmamos un payload con HMAC-SHA256 y lo POSTeamos a tu endpoint. Si tu endpoint falla, reintentamos con backoff exponencial.
Eventos disponibles
| Evento | Cuándo dispara |
|---|---|
message.received | Llegó un mensaje del cliente. |
message.sent | El worker envió un outbound (bot o API). |
message.delivered | WhatsApp confirmó entrega (2 checks). |
message.read | El destinatario abrió el chat (2 checks azules). |
message.failed | Envío falló tras todos los reintentos. |
session.connected | Sesión WhatsApp viva. |
session.disconnected | Sesión cayó (con reason). |
session.qr_generated | QR listo para escanear. |
session.logged_out | WhatsApp invalidó las credenciales. |
Crear un endpoint
Desde el dashboard → Webhooks → + Nuevo, o vía API:
curl -X POST https://app.replai.tech/api/v1/webhooks \
-H "authorization: Bearer rk_live_..." \
-H "content-type: application/json" \
-d '{
"name": "Mi sistema",
"url": "https://mi-app.com/replai/webhook",
"events": ["message.received", "message.failed"]
}'La respuesta contiene un secret — guárdalo ahí mismo, nunca te lo volveremos a mostrar.
Verificar la firma
Cada delivery viene con dos headers:
x-replai-timestamp: 1747401123 x-replai-signature: <hex-sha256>
La firma es HMAC-SHA256 de {timestamp}.{rawBody} usando tu secret.
Node:
import { verifyWebhookSignature } from '@replai/sdk';
app.post('/replai/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ok = verifyWebhookSignature({
rawBody: req.body, // ← Buffer, NOT parsed JSON
signatureHeader: req.header('x-replai-signature') ?? '',
timestampHeader: req.header('x-replai-timestamp') ?? '',
secret: process.env.WEBHOOK_SECRET!,
});
if (!ok) return res.status(401).send('bad signature');
const event = JSON.parse(req.body.toString());
// ... handle event ...
res.send({ ok: true });
});Python:
from replai import verify_webhook_signature
@app.post("/replai/webhook")
async def webhook(request: Request):
raw = await request.body()
if not verify_webhook_signature(
raw_body=raw,
signature_header=request.headers.get("x-replai-signature", ""),
timestamp_header=request.headers.get("x-replai-timestamp", ""),
secret=os.environ["WEBHOOK_SECRET"],
):
raise HTTPException(401)
...Reintentos
Replai considera la entrega exitosa con cualquier 2xx. Cualquier otra cosa (timeout, 4xx, 5xx) entra en cola de reintentos con backoff exponencial:
intento 1 → inmediato intento 2 → +30s intento 3 → +2min intento 4 → +10min intento 5 → +1h después → DLQ (dead-letter, sin más reintentos)
Si tu endpoint da 4xx permanente (404, 410), pausamos automáticamente el webhook a las 5 fallas seguidas para no spammearte. Lo reactivas desde el dashboard.
Protección contra replays
verifyWebhookSignature rechaza por defecto deliveries con timestamp fuera del rango de ±5 min (tolerancia configurable). Esto previene replays si alguien intercepta una request vieja.