Webhooks

Qué hace un webhook

Un webhook te envía los resultados en lugar de obligarte a pedirlos. Configura un endpoint HTTPS en un Flow y, cada vez que un Run termina, Tavnit envía las filas extraídas de ese Run a tu URL como un POST con JSON, normalmente pocos segundos después de que el Run termina.

La alternativa es el sondeo: llamar a la API cada cierto tiempo para preguntar si algo terminó. El sondeo consume solicitudes, agrega demora y empeora a medida que crece el volumen. Un webhook llega una sola vez, cuando hay algo que entregar.

Usa un webhook cuandoUsa otra opción cuando
Quieres los resultados en tu propio sistema en cuanto existenUna persona necesita leerlos: la salida por email es mejor
Estás conectando Tavnit con Make, Zapier, n8n o Power AutomateQuieres consultar los datos dentro de Tavnit: usa un Bucket
El volumen es tan alto que el sondeo resulta un desperdicioBuscas un Run concreto que ya conoces: llama a la API directamente

Qué funciones pueden enviar webhooks

Los Flows son el origen más común, pero la mayoría de las funciones que producen un resultado pueden enviarlo por POST a una URL. Cada una se configura en su propia página de detalle y envía su propio payload.

OrigenDónde se configuraCuándo se dispara
FlowPanel WebhookUn Run termina con éxito (después de la aprobación, si la revisión está activa)
CleanerPanel WebhookTermina una limpieza que el Cleaner ejecuta por su cuenta (Limpiar Conjunto de Datos y limpiezas por API). Los Runs de un Flow se entregan por el webhook del Flow.
Acción condicional de un CleanerUna acción de webhook en Acciones CondicionalesSe cumple una regla: una vez por regla por Run, no una vez por fila
AgenteEntrega, opción WebhookUn Run del Agente termina
MatcherPanel WebhookUn Match termina
InspectorPanel Webhook, y Llamar webhook en Si este check fallaTermina una Inspección; la acción por check se dispara por cada check que falla
FillerPanel WebhookUn Fill termina
SignalPanel WebhookTermina una Wave
NetPanel WebhookTermina un Catch con al menos una fila
PipelineUn nodo Salida configurado como Webhook, Zapier, Make, n8n, Slack, Teams o Google ChatLa ejecución llega al nodo Salida
Solo HTTPS

Los payloads contienen los datos de tus documentos, así que usa un endpoint https://. Los paneles Webhook no permiten guardar una URL http://.

Configura el webhook de un Flow

Los webhooks de Flow se configuran por Flow. Necesitas un endpoint HTTPS que acepte un POST con un cuerpo JSON.

  1. 1Consigue la URL de un endpoint. Las plataformas de automatización te dan una al crear un disparador de webhook; si no, expón tu propia ruta HTTPS.
  2. 2Abre el Flow en Flows.
  3. 3Abre el panel Webhook, pega la URL y guarda.
  4. 4Procesa un documento de prueba y confirma que llegó el POST. El registro del Run indica si la entrega tuvo éxito y qué código de estado se recibió.
La página de detalle de un Flow de Tavnit llamado Invoice Processor, con el panel izquierdo que muestra Email Trigger, Collections, Cleaner, Agent, Form Templates, Email Output, Webhook, Bucket Export y Human in the Loop, junto a los campos de metadatos y los campos de tabla del Flow.
Webhook está con las demás opciones de salida en el panel izquierdo de un Flow, junto a la Salida por Email y Exportar a Bucket.

Cómo es el payload de un Flow

El cuerpo es la salida del Run más sus identificadores. Las partidas repetidas llegan en rows, los campos de valor único en metadata, y run_id y flow_id te dicen qué Run los produjo.

JSON: cuerpo del webhook de un Flow
{
  "run_id": "8f1c2b7e-4d3a-4a91-9c11-2f7b6e0d5a44",
  "flow_id": "3a9d51c0-77b2-4e18-9f6d-0c4a1b8e2d63",
  "rows": [
    {
      "Description": "Software Platform Subscription — January",
      "Quantity": 1,
      "Price": 180.00,
      "Amount": 180.00,
      "Invoice Number": "001",
      "Issued Date": "2026-01-15",
      "Total": 270.30
    }
  ],
  "metadata": {
    "Invoice Number": "001",
    "Billed To": "Acme Ltd",
    "Total": 270.30
  }
}

Los nombres de campo dentro de rows y metadata son los que definiste en el Flow, así que el payload cambia de forma cuando cambias el esquema. Si hay un Cleaner conectado, recibes la salida limpia: monedas convertidas, columnas calculadas y todo lo demás. Si el Cleaner tiene un pivote aplicado al payload del webhook, las filas llegan en el formato pivotado (ancho).

ClaveSiempre presenteQué es
run_idSíEl Run que produjo este resultado.
flow_idSíEl Flow que procesó el documento.
rowsSíUna entrada por cada partida extraída. Un arreglo vacío es válido: algunos documentos no tienen tabla.
metadataSíCampos de valor único que describen el documento completo.
collection_run_idNoAparece cuando una Colección enrutó el documento a este Flow.
split_id, splitter_doc_titleNoAparece cuando un Splitter produjo este segmento.
JSON: claves de procedencia
{
  "run_id": "...",
  "flow_id": "...",

  // present when a Collection routed the document
  "collection_run_id": "b21e9f34-8c55-4d70-a6e2-91f0c7d43a18",

  // present when a Splitter produced this segment
  "split_id": "c74a0b12-3e69-4f85-b0d7-58e2a9c61f70",
  "splitter_doc_title": "Commercial Invoice",

  "rows": [],
  "metadata": {}
}
Los archivos llegan como enlaces, no como bytes

Los campos que contienen un archivo o una imagen no se incrustan en el JSON. Llegan como URLs temporales, porque los documentos almacenados son privados: una ruta de almacenamiento sin firmar no se podría descargar desde tu servidor. Descárgalos pronto en lugar de guardar el enlace.

Payloads de otros orígenes

Todos los orígenes envían JSON, pero las claves cambian. La mayoría incluye el resultado del origen más los identificadores que necesitas para encontrarlo en Tavnit.

OrigenClaves en el cuerpo
Limpieza de un CleanerLa salida limpia, más sweep_id, cleaner_id, y run_id cuando una limpieza por API vuelve a limpiar un Run.
Acción condicional de un Cleanercontent (el mensaje que redactaste, que puede incluir las filas que coinciden), receivers, run_id, flow_id, flow_name, sweep_id y matched_rows (cuántas filas coincidieron).
Agentebot_id, bot_run_id, status, inputs, output, y flow_run_id cuando un Run de Flow disparó al Agente.
MatcherEl resultado del Match, más match_id y matcher_id.
Inspectorinspection_id, inspector_id, verdict y el informe completo en output_json. Un webhook por check envía el check fallido en item en lugar del informe.
Fillerfill_id, filler_id, status, filled_forms (más filled_form_path para el primer formulario) y los valores de los campos en output_json.
SignalEl resultado de la Wave, más wave_id y signal_id.
Netcolumns, rows, catch_id, net_id, window_start, window_end y un resumen en stats.
Salida de un Pipelinepipeline_id, execution_id, pipeline_name, original_filename y outputs, con una entrada por cada nodo anterior.
JSON: cuerpo del webhook de un Agente
{
  "bot_id": "...",
  "bot_run_id": "...",
  "status": "completed",
  "flow_run_id": "...",   // solo cuando un Run de Flow disparó al Agente
  "inputs": { "supplier_name": "Acme Ltd" },
  "output": { ... }
}

El webhook de un Agente siempre incluye sus variables de entrada (nunca los secretos) para que tu receptor pueda unir la salida capturada con sus propios registros. El flow_run_id te dice de qué documento partía el Agente cuando lo inició un Run de Flow.

Una acción condicional se envía en cuanto se cumple la regla, antes de cualquier pausa de revisión. Es intencional: avísame cuando pase esto no debería esperar a un revisor. El webhook del Flow, en cambio, solo se dispara después de que un revisor aprueba.

Salida de un Pipeline a Slack, Teams y Google Chat

En un Pipeline, un nodo Salida envía los resultados de los nodos que lo alimentan al destino que elijas: Email, Slack, Teams, Google Chat, Webhook, Zapier, Make o n8n.

  • Slack, Teams y Google Chat reciben un mensaje legible con los campos clave, publicado mediante la URL de webhook entrante que copias de esa app.
  • Webhook, Zapier, Make y n8n reciben el JSON sin procesar que se muestra abajo.
  • Si un Flow del Pipeline ya envía su propio webhook, el Pipeline te avisa que una Salida publicaría el mismo resultado dos veces.
JSON: cuerpo de la Salida de un Pipeline
{
  "pipeline_id": "...",
  "execution_id": "...",
  "pipeline_name": "Supplier intake",
  "original_filename": "bundle.pdf",
  "outputs": [
    { "node": "Invoice Processor", "node_type": "flow", "output": { ... } }
  ]
}

Entrega, tiempos de espera y reintentos

Todos los orígenes siguen las mismas reglas de entrega. Tavnit espera hasta 10 segundos a que tu endpoint responda. Una falla de conexión o un tiempo agotado se reintenta una vez tras una pausa breve; una respuesta HTTP de error no se reintenta, porque tu servidor fue alcanzado y respondió.

Qué hace tu endpointQué hace Tavnit
Responde 2xx en menos de 10 segundosLa entrega queda registrada como enviada. Listo.
Rechaza la conexión, la corta o se agota el tiempoSe reintenta una vez tras una pausa breve. Si el reintento también falla, la entrega queda marcada como fallida.
Responde 4xx o 5xxNo se reintenta. El código de estado queda registrado para que veas qué respondió tu servidor.

No hay una cola de reintentos larga ni reenvío posterior de entregas fallidas. Si tu endpoint está caído durante una hora, esas entregas se pierden: los Runs igual tuvieron éxito y sus datos siguen en Tavnit, pero tendrás que obtenerlos por la API o reenviarlos de otra forma. Para lo que no te puedes permitir perder, combina el webhook con un Bucket para tener siempre una copia duradera.

Un webhook fallido nunca hace fallar el Run

La entrega es de mejor esfuerzo y está separada del procesamiento. Si tu endpoint no responde, el Run igual termina, los datos se guardan y todas las demás salidas (email, exportación a Bucket, llenado de formularios) se ejecutan. La excepción es el nodo Salida de un Pipeline: una entrega fallida marca ese nodo como fallido para que lo veas en la ejecución.

Cómo escribir un receptor

La regla más importante: confirma rápido y después trabaja. Diez segundos parecen suficientes hasta que tu manejador escribe en una base de datos lenta. Responde 200 en cuanto tengas el payload a salvo en una cola y haz el procesamiento real después.

Python (Flask)
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.post("/tavnit-webhook")
def receive():
    payload = request.get_json(silent=True) or {}

    run_id = payload.get("run_id")
    rows = payload.get("rows", [])

    # Acknowledge immediately. Tavnit waits 10 seconds for a response and
    # treats a timeout as a failed delivery, so queue the slow work instead
    # of doing it inline.
    enqueue_processing(run_id, rows)

    return jsonify({"received": True}), 200
JavaScript (Express)
import express from "express";

const app = express();
app.use(express.json({ limit: "10mb" }));

app.post("/tavnit-webhook", (req, res) => {
  const { run_id: runId, rows = [] } = req.body ?? {};

  // Respond inside the 10-second window, then do the work.
  res.status(200).json({ received: true });

  enqueueProcessing(runId, rows).catch(console.error);
});

app.listen(3000);
  • Acepta cuerpos razonablemente grandes: una factura larga con muchas partidas no es pequeña.
  • Trata la entrega como “al menos una vez”. Un reintento después de un tiempo agotado puede entregar el mismo resultado dos veces, así que haz tu manejador idempotente usando run_id (o el identificador propio del origen) como clave.
  • No asumas un esquema fijo. Lee los campos por nombre y tolera los que no reconozcas, para que agregar un campo al Flow no rompa tu receptor.
  • Registra el cuerpo sin procesar cuando algo falle. Es la única copia de lo que llegó.
Mantén la URL en secreto

La URL del endpoint es lo único que separa a internet de tus datos extraídos. Las plataformas de automatización incluyen un token secreto en la ruta justamente por eso. No la publiques y cámbiala si se filtra.

Solución de problemas

Empieza por el Run, no por tu servidor. Cada Run registra si se intentó la entrega del webhook, si tuvo éxito y qué código de estado o error se recibió, lo que te dice de inmediato si el problema está del lado de Tavnit o del tuyo.

SíntomaCausa probableSolución
No llega nada y no hay intento registradoEl Flow no tiene URL de webhook, o el Run falló antes de la entrega.Revisa el panel Webhook del Flow y el estado del Run.
La URL fue rechazada al guardarNo comienza con https://.Usa un endpoint HTTPS. No se acepta HTTP sin cifrar.
El webhook del Cleaner nunca se dispara en los Runs de un FlowLos webhooks de Cleaner solo se disparan en las limpiezas que el Cleaner ejecuta por su cuenta.Usa el panel Webhook del Flow; ya incluye la salida limpia.
Entrega registrada como fallida con un código de estadoTu endpoint respondió 4xx o 5xx. Fue alcanzado, así que no hubo reintento.Revisa los registros de tu servidor: normalmente el payload está bien y el manejador lanzó un error.
Entrega registrada como fallida por tiempo agotadoTu manejador tardó más de 10 segundos.Confirma primero y procesa de forma asíncrona, como se explicó arriba.
El mismo Run llegó dos vecesHubo un reintento después de un tiempo agotado en una solicitud que tu servidor sí procesó.Elimina duplicados usando run_id.
Los resultados llegan mucho más tarde de lo esperadoEl Flow tiene activada la Revisión Humana.El webhook se dispara al aprobar, no al extraer. Es así por diseño.