IdiomaENES
Integraciones

El webhook de sincronización

Un endpoint firmado por base de conocimiento. Qué es, cuándo usarlo, y todos sus campos, cabeceras y errores.

Actualizado 2026-09-06Ver .md

Qué es un webhook

Un webhook es una URL que le das a otro sistema para que te llame a ti cuando pasa algo. La alternativa es preguntar cada pocos minutos si hay novedades, que es más lento y casi siempre inútil. Un webhook es una petición, en el momento en que la cosa cambió de verdad.

Qué hace este

Cada base de conocimiento puede tener un endpoint firmado. Envías un documento y el documento se añade, se actualiza o se elimina. Ese es todo el contrato.

Existe para contenido que vive en algún sitio para el que no tenemos conector: un CMS interno, una base de Notion pasando por Zapier, una hoja de cálculo en Make, un script que se ejecuta después de tu build. Si ya usas WordPress o Webflow, usa esos conectores: saben qué cambió y gestionan los borrados por ti.

Cuándo usarlo y cuándo no

Quieres
Usa
Sincronizar un sitio WordPress o Webflow
Su conector
Rastrear una web pública
El rastreador
Empujar desde tu backend, con tu código
La API REST con una clave
Que Zapier, Make o n8n empujen, sin código
Este webhook

La diferencia con la API REST es solo cómo demuestras quién eres. La API REST usa una clave en una cabecera, que es lo correcto cuando controlas el código. Este usa una firma, que es lo correcto cuando quien llama es una herramienta sin código: la URL lleva un token público, así que la herramienta no necesita guardar credenciales, y la firma demuestra que el cuerpo no se manipuló.

Activarlo

Integraciones, luego Webhook de sincronización. Elige una base de conocimiento y pulsa Activar. Obtienes dos cosas:

  • El endpoint, https://app.welcomeai.dev/api/v1/kb/{kbId}/sync?t=whsec_…. El token de la URL identifica qué fuente se alimenta. Por sí solo no es un secreto: nombra, no autoriza.
  • El secreto de firma, que se muestra una sola vez. Guárdalo donde tu automatización guarde credenciales.

Rotar el secreto invalida el anterior al instante. No hay ventana de solape, así que cámbialo antes en tu automatización o asume unas peticiones fallidas.

Autenticación

Firma el cuerpo en crudo con HMAC-SHA256 y envía el resumen en hexadecimal.

X-WelcomeAI-Timestamp: 1757155200
X-WelcomeAI-Signature: sha256=<hex de "1757155200.<cuerpo en crudo>">

Lo que se firma es el timestamp, un punto literal y el cuerpo exactamente como viaja. Firma los bytes que envías: volver a serializar el JSON cambia los espacios y la firma dejará de coincidir.

Un timestamp de más de cinco minutos de antigüedad se rechaza, para que una petición capturada no pueda reproducirse después. Si tu herramienta no puede añadir cabeceras, omite el timestamp y firma solo el cuerpo; funciona igual y es lo que hace la mayoría de configuraciones de Zapier, pero renuncias a la protección contra reenvíos.

La petición

POST, Content-Type: application/json. Un documento, o un lote:

{
  "external_id": "faq-reembolsos",
  "title": "Reembolsos",
  "url": "https://acme.com/ayuda/reembolsos",
  "content_md": "# Reembolsos\n\nDentro de 14 días, escribe a billing@acme.com.",
  "metadata": { "categoria": "facturacion" }
}
{
  "items": [
    { "external_id": "faq-reembolsos", "title": "Reembolsos", "content_md": "…" },
    { "external_id": "faq-envios", "title": "Envíos", "content_md": "…" },
    { "external_id": "faq-viejo", "action": "delete" }
  ]
}

Campos

Campo
Obligatorio
Qué es
external_id
Tu identificador para este documento, hasta 500 caracteres. Volver a enviar el mismo lo actualiza en vez de crear una copia. Es el campo más importante de acertar.
content_md o content_html
sí, salvo al borrar
El contenido. Markdown preferido; el HTML se convierte. Hasta 2 MB.
title
no
Lo que muestra una cita. Vale la pena ponerlo: un "Sin título" en una cita hace que una respuesta correcta parezca equivocada.
url
no
Dónde puede verse el original. Se convierte en el enlace de la cita.
excerpt
no
Un resumen corto, guardado junto al documento.
metadata
no
Cualquier objeto JSON tuyo. Se conserva con el documento.
content_hash
no
Si lo envías, un hash sin cambios se salta la reindexación entera. Barato y recomendable en una sincronización grande.
action
no
upsert (por defecto) o delete.

Un lote son como mucho 25 elementos y el cuerpo entero como mucho 4 MB.

Borrar

Envía {"external_id": "…", "action": "delete"}. El documento, sus fragmentos y sus embeddings desaparecen. Borrar algo que nunca existió no es un error: devuelve missing y sigue, así que un borrado repetido es seguro.

La respuesta

200, siempre con el detalle por elemento. Un fallo parcial es un 200 con ok: false, no un 500: un elemento malo en un lote de veinticinco no debe obligarte a reenviar los otros veinticuatro.

{
  "ok": true,
  "accepted": 2,
  "failed": 0,
  "results": [
    { "external_id": "faq-reembolsos", "status": "created" },
    { "external_id": "faq-envios",     "status": "unchanged" }
  ]
}

status es created, updated, unchanged, deleted, missing o failed. Un elemento failed lleva un error que dice por qué.

`unchanged` es el que conviene mirar. Significa que el hash coincidía con lo que ya teníamos y no se reindexó nada. En una sincronización nocturna completa casi todo debería decir unchanged; si todo dice updated, algo en tu proceso está reescribiendo el contenido en cada pasada -- normalmente una marca de tiempo regenerada dentro del cuerpo -- y estás pagando por volver a incrustar el mismo texto.

Errores

Estado
Código
Qué pasó
400
invalid_json
El cuerpo no es JSON
400
invalid_input
Falta un campo o tiene la forma equivocada. El mensaje lo nombra
401
invalid_signature
La firma, el secreto o el timestamp están mal
404
not_found
No hay webhook activo para esa base, o el token no coincide
409
source_paused
La fuente está pausada. Reanúdala en Fuentes conectadas
413
payload_too_large
El cuerpo pasa de 4 MB

El 401 es el que sale de verdad. Por orden de probabilidad: se volvió a serializar el cuerpo antes de firmar; el timestamp se puso en la cabecera pero no en lo firmado, o al revés; se rotó el secreto; el reloj va desviado más de cinco minutos.

Reintentos

Nosotros no reintentamos: el que llama eres tú. Reintenta un 5xx o un fallo de red con espera creciente, y no reintentes un 4xx: nada de esa petición va a funcionar a la segunda.

Reintentar es seguro. external_id hace que cada escritura sea idempotente, así que enviar dos veces el mismo documento produce un documento, y la segunda llamada devuelve unchanged.

Límites

Hoy no hay límite de peticiones por endpoint. La ingesta la acotan los límites de documentos y almacenamiento de tu plan, y un lote está limitado a 25 elementos y 4 MB. Sé razonable en una primera importación: unas pocas peticiones por segundo, no unos cientos.

Mantenerlo seguro

  • Trata el secreto como una contraseña. Es lo único que separa tu base de conocimiento de cualquiera que tenga la URL, y la URL viaja por la herramienta donde la pegaste.
  • Rótalo cuando alguien se va, o cuando se retira una automatización.
  • Una base, un webhook. Si dos sistemas alimentan la misma base, dales bases separadas o prefijos de external_id distintos, para poder saber después cuál envió qué.
  • No envíes nada que un visitante no deba leer. Todo lo que publiques es contenido que el asistente puede citarle a quien pregunte.
  • Cada petición queda en el Registro de sincronización con qué encontró y qué cambió, que es donde mirar cuando el contenido no es el que esperabas.

Pon tu conocimiento a trabajar hoy

Gratis 14 dias. Sin tarjeta, y sin instalar nada en tu sitio.