BotDesk

Recibir webhooks

Suscribe una URL y recibe eventos firmados en tiempo real de tus bots — nuevas conversaciones, mensajes y escalados.

BotDesk puede enviar eventos HTTP firmados a tu propio endpoint a medida que ocurren cosas en una conversación: empieza una conversación nueva, se persiste un mensaje o el bot escala a una persona. Esto convierte la API pública de solo-consulta en una superficie de integración real: en lugar de hacer polling, recibes los eventos en el momento en que suceden.

Los webhooks salientes están disponibles en el plan Premium.

Autenticación

Todos los endpoints de gestión se autentican con tu API key pública en la cabecera X-API-Key (la misma clave que usa el resto de la API pública). Cada endpoint está acotado a tu cuenta: solo puedes ver o modificar tus propias suscripciones.

Gestionar suscripciones

Ruta base: /v1/public/v1/webhooks

Método Ruta Propósito
POST /webhooks Crear una suscripción. Devuelve el secreto de firma una sola vez.
GET /webhooks Listar tus suscripciones.
PATCH /webhooks/{id} Actualizar url, event_types o is_active.
DELETE /webhooks/{id} Eliminar una suscripción.
POST /webhooks/{id}/rotate-secret Generar un secreto de firma nuevo. Lo devuelve una vez.
GET /webhooks/{id}/deliveries Inspeccionar el historial de entregas (estado, código de respuesta, intentos).

Crear una suscripción

POST /v1/public/v1/webhooks
X-API-Key: <tu-api-key>
Content-Type: application/json

{
  "url": "https://tu-app.example.com/botdesk/webhooks",
  "event_types": ["conversation.created", "message.created", "escalation.requested"]
}

Respuesta (201 Created):

{
  "id": "…",
  "url": "https://tu-app.example.com/botdesk/webhooks",
  "event_types": ["conversation.created", "message.created", "escalation.requested"],
  "is_active": true,
  "created_at": "…",
  "updated_at": "…",
  "secret": "whsec_…"
}
  • La url debe ser HTTPS y resolver a una dirección pública. Las URLs que apuntan a rangos loopback, link-local, privados o reservados se rechazan con 422 (protección SSRF), tanto al crear como en cualquier cambio posterior de la URL.
  • Solo se aceptan los tres tipos de evento de abajo; un valor desconocido devuelve 422.
  • El secret se devuelve exactamente una vez, aquí y en rotate-secret. Guárdalo de forma segura: se usa para verificar firmas y no se vuelve a mostrar.

Eventos

Cada entrega es un POST JSON con un envelope versionado:

{
  "event": "message.created",
  "event_id": "b3f1c2a4-…",
  "version": "v1",
  "created_at": "2026-07-29T12:34:56.789012+00:00",
  "data": { }
}
Campo Significado
event El tipo de evento (uno de los tres de abajo).
event_id Id estable de este evento lógico. Úsalo para deduplicar (ver abajo).
version Versión del envelope, actualmente v1.
created_at Timestamp ISO-8601 en UTC de cuándo se generó el evento.
data Payload específico del evento.

conversation.created

Se dispara cuando se crea una conversación nueva en cualquier canal (widget, WhatsApp, Telegram, Instagram).

{ "conversation_id": "…", "channel": "whatsapp", "bot_id": "…" }

message.created

Se dispara cada vez que se persiste un mensaje en una conversación, tanto en el turno entrante del usuario como en el turno saliente del bot.

{ "conversation_id": "…", "message_id": "…", "role": "user", "content": "…" }

role es user o assistant. Nota: en el canal widget el mensaje del asistente se persiste primero como un placeholder vacío y su texto se finaliza un instante después; el evento lleva el cuerpo del placeholder en el momento de persistir. Reconcilia por message_id si necesitas la respuesta final.

escalation.requested

Se dispara cuando el bot escala una conversación a una persona (trigger explícito o implícito). Se incluye contact cuando se capturó información de contacto.

{ "conversation_id": "…", "contact": { "email": "cliente@example.com" } }

Verificar firmas

Cada entrega lleva estas cabeceras:

Cabecera Valor
X-BotDesk-Signature sha256=<hex> — HMAC-SHA256 del mensaje firmado.
X-BotDesk-Timestamp Timestamp Unix (segundos) usado en el mensaje firmado.
X-BotDesk-Event El tipo de evento, reflejando event del cuerpo.
X-BotDesk-Delivery Id único de este intento de entrega.

El mensaje firmado es el timestamp, un . literal y el cuerpo crudo de la petición:

signed_message = f"{X-BotDesk-Timestamp}." + <bytes crudos del cuerpo>
expected       = "sha256=" + HMAC_SHA256(secret, signed_message).hexdigest()

Compara expected con X-BotDesk-Signature usando una comparación de tiempo constante. Firma contra los bytes crudos que recibiste: no vuelvas a serializar el JSON primero, o las diferencias de espacios u orden de claves romperán la verificación.

Rechaza las entregas cuyo X-BotDesk-Timestamp esté fuera de tu tolerancia (por ejemplo ±5 minutos) para acotar el replay.

Ejemplo en Python:

import hashlib
import hmac

def verify(secret: str, headers: dict, raw_body: bytes) -> bool:
    timestamp = headers["X-BotDesk-Timestamp"]
    message = f"{timestamp}.".encode() + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, headers["X-BotDesk-Signature"])

Entrega, reintentos y deduplicación

  • Éxito es cualquier respuesta 2xx. Responde rápido (dentro de ~10 segundos) y haz el trabajo lento de forma asíncrona: BotDesk usa un timeout corto y no sigue redirects.
  • Los reintentos usan un schedule de backoff fijo tras el primer intento: 0s, 1m, 5m, 30m, 2h, 6h. Después de 6 intentos fallidos la entrega se marca dead y no se reintenta más. Inspecciona el estado con GET /webhooks/{id}/deliveries.
  • Deduplica por event_id. El mismo evento lógico lleva un event_id estable en cada reintento y en cada suscripción a la que se propaga. Trata la entrega como at-least-once: un evento puede llegar más de una vez, así que haz tu handler idempotente sobre event_id.
  • El orden no está garantizado. Usa created_at / message_id si necesitas ordenar los eventos de tu lado.

Rotar y desactivar

  • POST /webhooks/{id}/rotate-secret emite un secreto nuevo e invalida el anterior. Actualiza tu verificador antes de rotar (o inmediatamente después).
  • Pon is_active: false con PATCH para pausar las entregas sin eliminar la suscripción; vuelve a true para reanudar.