Documentación API

API REST para sincronizar datos estructurados con tu bot de Bravos AI.

Introducción

La Webhook API te permite sincronizar datos estructurados (JSON o XML) con un bot de Bravos AI. Puedes realizar una carga inicial desde una URL y mantener los datos actualizados mediante webhooks HTTP firmados con HMAC-SHA256.

Configuración

Antes de enviar webhooks, necesitas configurar la conexión desde Integraciones > Webhook personalizado en el panel de administración.

  1. 1

    Crea la conexión

    Dale un nombre descriptivo (ej: "Clínicas", "Inventario") y proporciona la URL donde están tus datos en formato JSON o XML (máximo 50 MB). El sistema descargará los datos y detectará los campos automáticamente.

  2. 2

    Elige el campo identificador

    Una vez creada la conexión, el sistema detecta los campos de tus datos. Selecciona cuál es el identificador único de cada registro (equivalente a una clave primaria). Por ejemplo: id, ref, sku. Este campo es obligatorio en cada item que envíes por webhook.

  3. 3

    Activa y recoge tus credenciales

    Al activar, el sistema procesa la carga inicial desde tu URL. A partir de ese momento tienes disponible:

    • Endpoint — la URL a la que enviar webhooks
    • Secret — la clave para firmar cada request con HMAC-SHA256
    • Dataset slug y Bot ID — ya incluidos en el endpoint

Una vez activada, puedes excluir campos desde el panel para que el chatbot no los use en sus respuestas.

Autenticación

Cada request debe incluir una firma HMAC-SHA256 en el header X-Bravos-Signature. Se calcula sobre el body completo usando tu secret. El formato es hex en minúsculas.

import hmac, hashlib

secret = "TU_SECRET_AQUI"
body = b'{"action":"upsert","items":[...]}'

signature = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
# Resultado: "a1b2c3d4..." (hex lowercase)

Si la firma no coincide, el servidor responde 401. Tras 10 intentos fallidos en un minuto, el endpoint se bloquea temporalmente.

Endpoint

POST https://api.bravos-ai.com/integrations/custom-webhook/{bot_id}/{dataset_slug}/sync

Los valores de bot_id y dataset_slug se generan al crear la conexión y están visibles en el panel de administración.

Headers obligatorios

Content-Type: application/json

X-Bravos-Signature: <firma HMAC-SHA256 en hex>

Crear y actualizar datos

Envía "action": "upsert" con un array de items. Si un item con el mismo identificador ya existe, se actualiza. Si no existe, se crea.

{
  "action": "upsert",
  "items": [
    {
      "ref": "MAD-01",
      "nombre": "Clínica Madrid Centro",
      "dirección": "Calle Gran Vía 30",
      "teléfono": "+34 910 000 000",
      "especialidades": ["Odontología", "Traumatología", "Dermatología"],
      "horario": {
        "lunes_viernes": "09:00 - 20:00",
        "sábados": "10:00 - 14:00"
      },
      "servicios": [
        {"nombre": "Limpieza dental", "precio": 60, "duración_min": 45},
        {"nombre": "Radiografía panorámica", "precio": 35, "duración_min": 15},
        {"nombre": "Consulta dermatológica", "precio": 80, "duración_min": 30}
      ]
    }
  ]
}

Cada item debe incluir el campo que elegiste como identificador al activar la conexión (en este ejemplo, ref). El resto de campos son libres y se detectan automáticamente.

Datos anidados

  • Objetos anidados se aplanan con punto: horario.lunes_viernes
  • Arrays de texto se unen: "Odontología, Traumatología, Dermatología"
  • Arrays de objetos se indexan como sub-registros, lo que permite al chatbot filtrar por sub-campos (ej: "servicios de menos de 50 euros")

Eliminar datos

Envía "action": "delete" con los identificadores de los registros a eliminar. La eliminación incluye los sub-registros asociados.

{
  "action": "delete",
  "items": [
    { "ref": "MAD-01" }
  ]
}

Eliminar un registro inexistente no produce error. La operación es idempotente.

Respuestas

Éxito (200)

{
  "ok": true,
  "request_id": "req_7a3f1b2c",
  "action": "upsert",
  "stats": {
    "processed": 1,
    "created": 1,
    "updated": 0,
    "skipped": 0,
    "children_created": 3,
    "children_deleted": 0,
    "chunks_regenerated": 1
  }
}
  • skipped — el contenido del item no cambió (no se regeneran embeddings)
  • children_created/deleted — sub-registros de arrays de objetos
  • chunks_regenerated — textos recalculados para búsqueda semántica

Error (4xx/5xx)

{
  "detail": "Invalid signature"
}

El campo detail describe el error en inglés. Los códigos HTTP indican la categoría (ver tabla de errores abajo).

Códigos de error

CódigoCausaQué hacer
400JSON malformado, falta el campo identificador, o estructura inválidaRevisa el payload. No reintentes sin corregir.
401La firma HMAC no coincideVerifica que usas el secret correcto y firmas el body exacto que envías.
403Request enviado por HTTP en vez de HTTPSUsa siempre HTTPS.
404El bot o dataset no existeComprueba bot_id y dataset_slug en el panel.
409El dataset no está activado todavíaCompleta la activación en el panel de administración.
413El body supera 5 MBDivide el envío en batches más pequeños.
429Otro webhook se está procesando o demasiados intentos fallidosEspera 30 segundos y reintenta.
500Error interno del servidorReintenta con backoff exponencial (1s, 2s, 4s, 8s, máx. 5 intentos).

Límites

50 MB

Carga inicial (URL)

5 MB

Body por webhook

500

Items por webhook

HTTPS

Obligatorio

Actualizaciones masivas: si necesitas actualizar más de 500 registros vía webhook, envíalos en batches secuenciales de hasta 500 cada uno. La carga inicial por URL no tiene este límite.

Reintentos: ante un 5xx, reintenta con backoff exponencial. Ante un 4xx, corrige el payload antes de reintentar. Ante un 429, espera 30 segundos.

Idempotencia: enviar el mismo upsert dos veces no duplica datos — el segundo request actualiza lo que ya existe.

Ejemplo completo

Un script completo que envía datos, firma con HMAC y maneja la respuesta.

import hmac, hashlib, json, requests

SECRET = "TU_SECRET_AQUI"
ENDPOINT = "https://api.bravos-ai.com/integrations/custom-webhook/{BOT_ID}/{DATASET_SLUG}/sync"

clinicas = [
    {
        "ref": "MAD-01",
        "nombre": "Clínica Madrid Centro",
        "dirección": "Calle Gran Vía 30",
        "servicios": [
            {"nombre": "Limpieza dental", "precio": 60},
            {"nombre": "Consulta dermatológica", "precio": 80},
        ],
    },
    {
        "ref": "BCN-01",
        "nombre": "Clínica Barcelona Eixample",
        "dirección": "Passeig de Gràcia 55",
        "servicios": [
            {"nombre": "Revisión general", "precio": 50},
            {"nombre": "Fisioterapia", "precio": 45},
        ],
    },
]

body = json.dumps({"action": "upsert", "items": clinicas}).encode()
signature = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()

r = requests.post(ENDPOINT, data=body, headers={
    "Content-Type": "application/json",
    "X-Bravos-Signature": signature,
}, timeout=30)

data = r.json()
if data.get("ok"):
    print(f"Procesados: {data['stats']['processed']}")
    print(f"Creados: {data['stats']['created']}, Actualizados: {data['stats']['updated']}")
else:
    print(f"Error {r.status_code}: {data.get('detail')}")