Webhooks
Registra un endpoint público, verifica payloads sin procesar firmados, deduplica eventos y gestiona los reintentos de forma segura.
Registra un endpoint público, verifica payloads sin procesar firmados, deduplica eventos y gestiona los reintentos de forma segura.
Los webhooks para desarrolladores envían a tu servidor los eventos de tu cuenta, de modo que no necesitas hacer polling. La entrega es de al menos una vez: los eventos pueden duplicarse, retrasarse o llegar desordenados.
Registrar un endpoint#
Crea una clave con webhooks:write y luego registra un receptor HTTPS público junto con el conjunto mínimo de eventos que necesites.
curl --fail-with-body https://makeagent.fast/api/v1/webhook-endpoints \
-X POST \
-H "Authorization: Bearer $MAF_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: webhook-production-v1" \
-d '{
"url": "https://api.example.com/webhooks/make-agent-fast",
"events": ["lead.created", "conversation.started"]
}'La URL debe usar HTTPS, no puede incluir usuario, contraseña, fragmento ni puerto personalizado, y debe resolver únicamente a direcciones IP públicas. Durante la entrega no se siguen las redirecciones.
La respuesta 201 revela una sola vez un secreto de firma whsec_.... Guárdalo en el gestor de secretos del receptor antes de cerrar la respuesta. Si más adelante listas los endpoints, obtendrás la URL, los eventos, el estado, la última entrega y el último error, pero no el secreto.
Tipos de eventos#
| Evento | Se emite cuando |
|---|---|
site.published | Un sitio pasa a estar publicado |
site.unpublished | Un sitio vuelve al estado de borrador |
lead.created | El agente captura un nuevo lead |
lead.qualified | El agente califica un lead, o un lead se acepta en el pipeline de ventas |
booking.requested | Un visitante solicita o reserva un horario |
handoff.requested | El agente transfiere una conversación a una persona (handoff) |
opportunity.created | Un lead se convierte en una oportunidad del pipeline |
opportunity.updated | Cambia la asignación, el valor, la siguiente acción u otros campos de una oportunidad |
opportunity.stage_changed | Una oportunidad pasa a otra etapa |
opportunity.won | Una oportunidad se cierra como ganada |
opportunity.lost | Una oportunidad se cierra como perdida |
conversation.started | Comienza un nuevo hilo de conversación |
conversation.message.created | Se añade un mensaje a una conversación |
knowledge.ready | Una fuente de conocimiento termina de procesarse |
broadcast.sent | Un broadcast termina su flujo de envío |
domain.activated | Retirado: no se ofrecen dominios personalizados; este evento nunca se emite |
Formato de la solicitud#
Make Agent Fast envía un POST con Content-Type: application/json, User-Agent: MakeAgentFast-Webhooks/1.0 y estas cabeceras:
maf-event-id: evt_...
maf-event-type: lead.created
maf-signature: t=1784210566,v1=HEX_HMAC_SHA256El cuerpo JSON tiene un envelope estable. Los campos dentro de data dependen del evento y pueden recibir campos adicionales.
{
"id": "evt_2c34...",
"type": "lead.created",
"created_at": "2026-07-16T10:02:46.000Z",
"data": {
"site_id": "SITE_ID",
"lead_id": "LEAD_ID"
}
}Usa el id del cuerpo o maf-event-id como clave de deduplicación. No uses la hora de entrega ni un ID de base de datos generado.
Verificar la firma en Node.js#
Lee los bytes exactos sin procesar antes de parsear el JSON. El valor firmado es TIMESTAMP + "." + RAW_BODY.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(rawBody: Buffer, header: string, secret: string) {
const values = Object.fromEntries(header.split(",").map((part) => part.split("=", 2)));
const timestamp = values.t;
const supplied = values.v1;
if (!timestamp || !supplied || !/^[a-f0-9]{64}$/.test(supplied)) return false;
const age = Math.abs(Date.now() / 1_000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest("hex");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(supplied, "hex"));
}Rechaza las firmas mal formadas y las marcas de tiempo anteriores a tu ventana de repetición aceptada; cinco minutos es un valor predeterminado razonable. Compara bytes de longitud fija en tiempo constante.
Verificar la firma en Python#
import hashlib
import hmac
import time
def verify_webhook(raw_body: bytes, header: str, secret: str) -> bool:
values = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
timestamp = values.get("t", "")
supplied = values.get("v1", "")
try:
if abs(time.time() - int(timestamp)) > 300:
return False
except ValueError:
return False
signed = timestamp.encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, supplied)Parsea y procesa el JSON solo después de que la verificación de la firma haya sido correcta.
Confirmar y procesar#
- Verifica la firma y la marca de tiempo.
- Inserta el ID del evento en una tabla con una restricción de unicidad.
- Si ya existe, devuelve
204sin repetir el efecto secundario. - Confirma el evento (commit) o encola tu tarea interna.
- Devuelve rápidamente una respuesta
2xx.
El emisor de Make Agent Fast agota el tiempo de espera a los 10 segundos. Solo considera correcta una respuesta 2xx y no sigue redirecciones.
Comportamiento de los reintentos#
Las entregas fallidas se encolan hasta un máximo de ocho intentos. El backoff comienza en unos dos segundos, se duplica con jitter y tiene un tope de una hora; la hora real de entrega puede ser posterior cuando los workers están ocupados. Como los reintentos pueden sobrevivir a la solicitud que los originó y llegar desordenados, nunca dependas de que un evento llegue inmediatamente antes que otro.
Devuelve una respuesta que no sea 2xx solo cuando quieras que Make Agent Fast reintente. Si un evento tiene una versión que ya no se admite de forma permanente o el destino fue eliminado, acepta y registra el evento, o elimina el endpoint, en lugar de generar fallos transitorios interminables.
Rotar un secreto de firma#
Los secretos de firma no se pueden revelar ni editar. Para rotarlos de forma segura:
- Crea un segundo endpoint que apunte a una ruta de receptor temporal o con versión.
- Guarda el secreto recién revelado.
- Acepta y deduplica los eventos de ambos endpoints.
- Comprueba que el nuevo endpoint recibe entregas válidas.
- Elimina el endpoint antiguo con
DELETE /webhook-endpoints/{endpoint_id}.
Si la URL del receptor se mantiene igual, haz que la aplicación receptora acepte ambos secretos durante el periodo de solapamiento y utiliza los IDs de evento para evitar efectos secundarios duplicados.
Solución de problemas#
| Síntoma | Qué revisar |
|---|---|
| No hay entregas | Estado del endpoint, eventos seleccionados, DNS público, certificado HTTPS y si el evento realmente ocurrió |
| La firma no coincide | Acceso al cuerpo sin procesar, concatenación exacta de timestamp.body, secreto correcto del endpoint y transformaciones del cuerpo por parte de proxies |
| Entregas repetidas | Código de respuesta, tiempo de espera de 10 segundos, excepciones internas y unicidad del ID de evento |
| Error de dirección privada | Los registros DNS deben resolver solo a direcciones públicas; localhost y los rangos internos se rechazan |
| Fallo por redirección | Registra directamente la URL HTTPS final; las redirecciones no se siguen |
Nunca incluyas el secreto de firma, la clave bearer ni el payload completo de un visitante en un mensaje a soporte. Incluye el ID del endpoint, el ID del evento, la hora del fallo y los logs del receptor sanitizados.