Webhooks firmados
Cómo recibir eventos de Orkestal en tu servidor, cómo verificar su firma y cómo funcionan las entregas, los reintentos y la desactivación.
Un webhook es un aviso que Orkestal manda a una dirección web tuya cuando pasa algo. Hay tres fuentes, que comparten la misma forma de firmar:
| Fuente | Cuándo se manda | Dónde se configura |
|---|---|---|
| Acción «Enviar a webhook (n8n)» de un flujo | Cuando se cumple el «Cuando» del flujo; manda el lead completo | Flujos: Hacer |
| Suscripciones de WhatsApp API | Por cada cambio que Meta notifica de tu número | WhatsApp API |
| Agente externo | Por cada mensaje de un cliente, para que tu agente conteste | Agente externo |
Requisitos de tu URL#
- Debe ser pública y usar
https://. Orkestal rechaza direcciones de red interna (por seguridad) y no sigue redirecciones. - Responde con un
2xxen menos de 10 segundos. Si tu proceso tarda más, responde primero y procesa después.
Cabeceras#
Si configuraste un secreto, cada envío lleva:
| Cabecera | Contenido |
|---|---|
X-Orkestal-Signature | La firma, con el formato sha256=… |
X-Orkestal-Timestamp | Cuándo se envió (segundos Unix) |
X-Orkestal-Event | El nombre del evento (en los envíos de flujos) |
X-Orkestal-Delivery | El identificador de la entrega (en las suscripciones de WhatsApp API) |
Verificar la firma#
La firma es el HMAC-SHA256 de la cadena "{timestamp}.{cuerpo}", calculado con tu secreto y escrito en hexadecimal, con el prefijo sha256=.
Python#
import hmac, hashlib, time
def is_valid(headers, raw_body: bytes, secret: str) -> bool:
ts = headers["X-Orkestal-Timestamp"]
if abs(time.time() - int(ts)) > 300:
return False # protección contra replay
expected = "sha256=" + hmac.new(
secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, headers["X-Orkestal-Signature"])Node.js#
const crypto = require('crypto');
function isValid(headers, rawBody, secret) {
const ts = headers['x-orkestal-timestamp'];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${ts}.${rawBody}`)
.digest('hex');
const given = headers['x-orkestal-signature'] || '';
return given.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}En n8n#
En el nodo Webhook activa Raw Body. Después del Webhook, agrega un nodo Code que recalcule la firma con el cuerpo crudo y lance un error si no coincide. Si tu instalación no permite verificarla, coloca un receptor verificador delante del workflow.
Entregas de WhatsApp API#
Cada cambio que Meta notifica llega como un POST con esta forma:
{
"event": "whatsapp.webhook",
"delivery_id": "6f1c…",
"business_id": "…",
"created_at": "2026-10-28T18:00:00Z",
"data": {
"object": "whatsapp_business_account",
"entry": [{
"id": "<WABA_ID>",
"changes": [{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": { "display_phone_number": "5215512345678", "phone_number_id": "…" },
"contacts": [{ "profile": { "name": "Ana" }, "wa_id": "5215598765432" }],
"messages": [{ "from": "5215598765432", "id": "wamid.…", "timestamp": "…",
"type": "text", "text": { "body": "Hola" } }]
}
}]
}]
}
}El campo data tiene el formato exacto de un webhook de la Cloud API de Meta, con un solo cambio por entrega. La lista de field posibles está en WhatsApp API.
Reintentos y desactivación#
- Si no respondes
2xx, Orkestal reintenta 8 veces durante aproximadamente un día: a los 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h y 12 h. - Tras 50 fallos seguidos, el webhook se desactiva y te avisamos. Reactívalo desde el panel.
- El mismo evento puede llegar más de una vez y fuera de orden. Deduplica por
delivery_id(o por eliddel mensaje). - Cada suscripción admite payloads de hasta 5 MB, y puedes tener hasta 3 suscripciones por negocio.
Probar#
Usa el botón Probar del panel, o POST /webhook-subscriptions/{id}/test: recibirás un evento test.ping firmado. En Entregas (o GET /webhook-subscriptions/deliveries) ves el resultado de cada envío y puedes reintentar los fallidos.
Envíos de flujos#
La acción «Enviar a webhook (n8n)» manda el lead en JSON (contacto, datos recolectados, etapa, último mensaje). El cuerpo completo y un ejemplo están en n8n. Si pones secreto, el envío va firmado igual que arriba; si no, llega sin cabeceras de firma.
Buenas prácticas#
- Verifica siempre la firma antes de actuar: sin ella, cualquiera que conozca tu URL podría mandarte datos falsos.
- Responde rápido y procesa en segundo plano.
- Hazlo idempotente: guarda los
delivery_idoevent_idque ya procesaste. - Guarda el secreto como variable de entorno, nunca en el código.
- Rota el secreto (
rotate-secret) si sospechas que se expuso.