Recibir webhooks

Haz que los elementos nuevos lleguen a tu propio extremo en cuanto se guardan. Que un elemento pase a ser buscable (o no lo consiga) y que un elemento se borre no se pueden saber por sondeo y solo llegan por esta vía. El destino se configura en tu página de cuenta.

Cabeceras

Se envían en cada entrega. Verificarlas es opcional.

CabeceraEjemploQué es
webhook-idmsg_2KWPBg…Clave de idempotencia. Idéntica en todos los reintentos de un evento.
webhook-timestamp1614265330Segundos unix. Comprueba que sea reciente para rechazar reenvíos.
webhook-signaturev1,g0hM9SsE…HMAC-SHA256 del cuerpo, en base64.
content-typeapplication/jsonSiempre este valor.
tu nombre de cabecera de autenticacióntu valor de cabecera de autenticaciónTambién se envía, si registraste una.
POST <your endpoint>
content-type: application/json
webhook-id: msg_2KWPBgLlAfxdpx2AI54pPJ85f4W
webhook-timestamp: 1614265330
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=

Cuerpo

El sobre no cambia nunca; type decide la forma de data.

typedataCuándo
note.createdun elemento, con su contenidoSe guarda un elemento. No significa que sea buscable: mira status.
note.ready{ count, group_id?, items[] }Elementos guardados pasan a ser buscables. Un evento por grupo y por pasada de procesamiento.
note.failed{ count, group_id?, items[] }Elementos guardados no se pudieron hacer buscables. Siguen ahí.
note.deleted{ count, items[{id}] }Se eliminan elementos. Solo ids: ya no existen.
webhook.ping{}Pulsas Probar la conexión.

Un guardado suelto

{
  "type": "note.created",
  "timestamp": "2026-09-05T04:12:07Z",
  "data": {
    "id": 41,
    "title": "…",
    "summary": "…",
    "content": "…",
    "created_at": "2026-09-05T04:11:58Z",
    "origin": "Claude",
    "status": "ready",
    "group_id": null
  }
}

Una parte de una subida

{
  "type": "note.created",
  "timestamp": "2026-09-05T04:12:09Z",
  "data": {
    "id": 42,
    "title": "… (3/77, p.47–53)",
    "summary": "…",
    "content": "…",
    "created_at": "2026-09-05T04:12:08Z",
    "origin": "Upload",
    "status": "pending",
    "group_id": "9f2c1ab84e7d4f0b93c5a61d2e8b7c40"
  }
}

Partes de una subida pasaron a ser buscables

{
  "type": "note.ready",
  "timestamp": "2026-09-05T04:31:12Z",
  "data": {
    "count": 2,
    "group_id": "9f2c1ab84e7d4f0b93c5a61d2e8b7c40",
    "items": [
      { "id": 42, "title": "… (3/77, p.47–53)", "status": "ready" },
      { "id": 43, "title": "… (4/77, p.54–60)", "status": "ready" }
    ]
  }
}

Partes de una subida no se pudieron hacer buscables

{
  "type": "note.failed",
  "timestamp": "2026-09-05T04:36:40Z",
  "data": {
    "count": 2,
    "group_id": "9f2c1ab84e7d4f0b93c5a61d2e8b7c40",
    "items": [
      { "id": 44, "title": "… (5/77, p.61–67)", "status": "failed" },
      { "id": 45, "title": "… (6/77, p.68–74)", "status": "failed" }
    ]
  }
}

Un archivo puede quedar en parte buscable y en parte no: entonces ambos eventos llegan con segundos de diferencia y el mismo group_id.

Se borraron elementos

{
  "type": "note.deleted",
  "timestamp": "2026-09-05T05:02:44Z",
  "data": { "count": 2, "items": [{ "id": 42 }, { "id": 43 }] }
}

La prueba de conexión

{
  "type": "webhook.ping",
  "timestamp": "2026-09-05T05:10:00Z",
  "data": {}
}

Guardados sueltos y subidas

Los dos flujos usan los mismos tipos de evento; status y group_id te dicen cuál tienes.

Un guardado suelto: desde MCP, la API REST o tu panel. Un solo note.created, y ya es buscable en cuanto llega.

Una subida: el archivo se divide en partes y cada parte recibe su propio note.created. Ninguna es buscable todavía; note.ready llega después, cuando ya lo son.

Un guardado sueltoUna subida (un archivo)
note.createduno, status: "ready", group_id: nulluno por parte, status: "pending", con group_id
note.readyno llega nuncallega cuando las partes pasan a ser buscables
note.failedno llega nuncallega si alguna parte no se pudo hacer buscable
Buscable desdenote.creatednote.ready
Legible desdede inmediatode inmediato: lo único que espera es la búsqueda
Número de parteno es un campo. Solo el (3/77) del título y el orden de data.id
  • status: si el elemento es buscable. "ready" significa que sí; "pending", que aún se está preparando; "failed", que su preparación falló y la búsqueda no lo encontrará.
  • group_id: el nombre de un lote. Se emite al subir un archivo y todas las partes de ese archivo llevan el mismo valor. Dice a qué archivo pertenece una parte, no qué número de parte es, y es la clave que enlaza un note.created con el note.ready que viene después.
  • Si solo quieres elementos que puedas buscar: actúa con note.ready y salta cualquier note.created cuyo status sea "pending".
  • Una parte pendiente se puede leer de inmediato: su contenido ya viene en la carga. Para volver a leerlo, usa GET /api/v1/notes/{id}, que no cuenta como apertura, o GET /api/v1/notes/group/{group_id} para el lote entero.
  • note.ready no llega una vez por archivo: cada uno lleva las partes que quedaron listas a la vez, así que un archivo cuyo procesamiento se divide, o una parte que hubo que reintentar, produce varios eventos con el mismo group_id. count es lo que llevaba ese evento, no el total del archivo, así que acumula items.
  • note.failed no significa que el elemento se haya borrado: sigue guardado y se puede leer, y solo falló su preparación para la búsqueda. No lo quites de tu copia; el borrado tiene su propio evento, note.deleted. Pulsa Reintentar en tu panel para prepararlo de nuevo, y si vuelve a fallar llegará un note.failed nuevo.

Cómo escribir el receptor

Responde 2xx de inmediato y haz el trabajo después, en tu propia cola.

Haz estoSi no
Devuelve 2xx ya, procesa despuésA los 25 segundos damos por fallida la entrega y la repetimos.
Descarta duplicados con webhook-idProcesarás el mismo elemento dos veces.
Responde a un duplicado con 200, nunca 429429 significa «más despacio», así que lo reenviamos.
Ordena por data.idUn reintento puede adelantar a un evento posterior; el orden no está garantizado.
Registra la URL finalNo seguimos redirecciones: un 3xx termina la entrega.

Verificar la firma (opcional)

Confirma que la entrega viene de nosotros y que el cuerpo no se alteró por el camino.

  1. Construye la cadena firmada: {webhook-id}.{webhook-timestamp}.{cuerpo en bruto}
  2. Obtén la clave: quita el prefijo whsec_ del secreto y descodifica el resto en base64
  3. HMAC-SHA256, codifica en base64 y compara con la parte que sigue a v1,
  4. Comprueba que webhook-timestamp esté dentro de unos minutos respecto a ahora
  5. Verifica los bytes en bruto que recibiste: analizar el JSON y volver a serializarlo rompe la firma
  6. Cualquier biblioteca de Standard Webhooks (standardwebhooks.com) hace los pasos 1 a 5 por ti

Tu secreto de firma está en la página de cuenta, donde puedes copiarlo o cambiarlo.

Reintentos

Si una entrega falla la enviamos hasta tres veces más (cuatro intentos en total). Solo 2xx cuenta como entregado.

RespuestaQué hacemos
2xxListo.
408 · 429 · 5xxReintentamos a los 15 minutos, luego a la hora y luego a las 3 horas.
Ninguna respuestaReintentamos con el mismo calendario.
3xx y cualquier otro 4xxParamos aquí: la respuesta no va a cambiar.

Las entregas nunca se desactivan solas. Mientras siga fallando te avisamos por correo como mucho una vez al día, y tu página de cuenta muestra la racha de fallos y el último error.

Si tu receptor estuvo caído más de unas cuatro horas, ponte al día con GET /api/v1/notes/changes.

Más

Registrar un destino, la clave de firma y la prueba de conexión están todos en tu página de cuenta. Las claves, los extremos y los errores de la API REST están en la documentación de la API REST. Las preguntas frecuentes tienen respuesta en las FAQ y, si algo no queda claro o no funciona, escríbenos: una persona real lee todos los mensajes.