Has creado un trabajo de aprovisionamiento y has recibido un 202: un ID de motor y status: "in_progress". El agente de IA está recorriendo ahora tus fuentes y aplicando voces de marca, elementos del glosario e instrucciones a ese motor en segundo plano. Puede tardar un instante o bastante más, según cuántos enlaces tenga que rastrear. Podrías dejar abierto un WebSocket en tiempo real y ver cómo trabaja, pero preferirías no mantener una conexión abierta solo para saber cuándo termina el agente y qué ha creado.
Para eso está el webhook. Cuando pasas una callbackUrl al crear el trabajo, Lingo envía por POST el resultado final a esa URL en cuanto el trabajo termina: te avisa cuando el motor está listo y te entrega el inventario de lo que se ha creado. Si el trabajo termina correctamente, llega como provisioning.completed con el resumen de cada registro que ha creado la IA. Si falla, llega como provisioning.failed con el motivo. En ambos casos, tu flujo de configuración se entera sin tener que preguntar.
Esta página cubre las dos cargas útiles y cómo gestionarlas. La entrega va firmada y se reintenta; ese mecanismo se comparte con la localización y está documentado en la página de verificación de firma de webhooks, enlazada justo donde la necesitas.
En esta página
- Cómo funciona la entrega
- La carga útil completed
- La carga útil failed
- Cómo gestionar un webhook
- Cuándo la entrega no es la herramienta adecuada
Cómo funciona la entrega#
Un trabajo de aprovisionamiento termina exactamente una vez. En el instante en que alcanza un estado final —todas las fuentes se han rastreado y analizado, o la ejecución se ha abandonado—, su resultado se entrega a tu callbackUrl en un único POST. Un grupo de localización se divide en un trabajo por cada idioma de destino, y cada uno entrega su propio callback; un trabajo de aprovisionamiento es un solo trabajo, así que hay una sola entrega.
Configura el destino con callbackUrl cuando crees el trabajo. Por el canal viajan dos formatos de carga útil, distinguidos por su campo type: provisioning.completed y provisioning.failed. Ambos indican el jobId y el engineId a los que pertenecen, para que un único handler pueda enrutar por type y actualizar el registro correcto.
Solo HTTPS
callbackUrl debe usar HTTPS. Una URL HTTP se rechaza al crear el trabajo: el webhook va firmado, y enviar una carga útil firmada sobre texto sin cifrar anula por completo su propósito.
Gestiona bien los tipos de evento desconocidos
Hoy por hoy, por el canal viajan provisioning.completed y provisioning.failed. Trata ese conjunto como abierto: bifurca según los tipos que conoces e ignora el resto, para que un tipo de evento futuro no rompa un handler ya desplegado.
La carga útil completed#
Cuando el trabajo termina, la carga útil incluye el summary: el mismo inventario que obtendrías al consultar el trabajo, pero enviado a ti en lugar de tener que ir preguntando. Enumera cada voz de marca, elemento del glosario e instrucción que la IA ha creado en tu motor, y también cualquier error por elemento que haya surgido durante el proceso.
{
"type": "provisioning.completed",
"jobId": "pjb_A1b2C3d4E5f6G7h8",
"engineId": "eng_X1y2Z3a4B5c6D7e8",
"summary": {
"brandVoices": { "count": 3, "ids": ["bv_A1b2C3d4", "bv_B2c3D4e5", "bv_C3d4E5f6"] },
"glossaryItems": { "count": 12, "ids": ["gi_A1b2C3d4", "..."] },
"instructions": { "count": 5, "ids": ["ins_A1b2C3d4", "..."] },
"errors": []
}
}| Campo | Descripción |
|---|---|
type | provisioning.completed |
jobId | El trabajo de aprovisionamiento que ha terminado (prefijo pjb_) |
engineId | El motor que ha configurado (prefijo eng_) |
summary | Lo que la IA ha creado en el motor: recuentos e IDs por componente, además de errores por elemento en errors |
El summary es el mismo objeto que incluye el trabajo, y su significado campo por campo —qué es cada componente, cómo se asignan los elementos a los idiomas, qué acaba en errors— está documentado una sola vez en Qué extrae la IA. Aquí basta con saber que la carga útil completed te entrega los IDs de todo lo que ha creado el agente, para que tu handler pueda registrarlos o mostrarlos en tu panel sin volver a consultar el trabajo.
Un array errors no vacío sigue llegando como completed.
Los errores por elemento no hacen que falle el trabajo. Si una fuente no se puede rastrear o un registro no se puede crear, aparece en summary.errors y el resto se sigue aplicando al motor; y la carga útil sigue siendo provisioning.completed, no provisioning.failed. El evento completed significa que el trabajo llegó hasta el final; lee errors para ver qué hay que corregir. Se envía una carga útil provisioning.failed cuando la ejecución no produce ningún motor utilizable.
La carga útil failed#
Un trabajo de aprovisionamiento falla cuando la ejecución no produce nada con lo que trabajar; por ejemplo, si ninguna fuente se puede rastrear, el agente no tiene contenido que analizar. Cuando eso ocurre, también se te informa. El tipo de carga útil es provisioning.failed y lleva una cadena error en lugar del resumen:
{
"type": "provisioning.failed",
"jobId": "pjb_A1b2C3d4E5f6G7h8",
"engineId": "eng_X1y2Z3a4B5c6D7e8",
"error": "All sources failed to crawl. No content available for analysis."
}| Campo | Descripción |
|---|---|
type | provisioning.failed |
jobId | El trabajo de aprovisionamiento que ha fallado |
engineId | El motor que se creó, pero quedó sin configurar |
error | Motivo legible por personas por el que el trabajo no pudo completarse |
Aquí llega la pregunta lógica de cualquier lector escéptico: si el trabajo ha fallado, ¿he perdido también el motor? No. El engineId de esta carga útil es el mismo motor que recibiste en el 202: sigue existiendo, se creó en el momento en que hiciste la llamada, solo que sin la configuración que habría añadido la ejecución fallida. Un fallo te hace perder la extracción, nunca el motor. Ajusta lo que enviaste y vuelve a intentarlo, o configura el motor manualmente desde el panel. Cuando un trabajo falla durante el rastreo, normalmente el problema está en las fuentes; Tipos de fuentes explica qué hace que una fuente merezca la pena.
Cómo gestionar un webhook#
La primera reacción de un lector escéptico aquí es la correcta: mi handler hace trabajo real —una escritura en base de datos, una notificación, una actualización del panel—, así que ¿no mantendrá la conexión abierta lo suficiente como para agotar el tiempo de espera del webhook?
Sí, así que no hagas esperar a Lingo. Devuelve 200 primero; procesa después. Confirma la recepción y haz el trabajo real una vez enviada la respuesta. El contrato completo de entrega —por qué primero confirmas y cuál es la programación de reintentos si no lo haces— está en la página de firma y entrega; el handler de abajo muestra qué forma adopta para una carga útil de aprovisionamiento.
app.post("/webhooks/provisioning", verifyWebhook, async (req, res) => {
// Acknowledge first - the job ends once, so this fires once.
res.status(200).send("ok");
const { type, jobId, engineId } = req.body;
if (type === "provisioning.completed") {
const { summary } = req.body;
await db.engines.update({
where: { engineId },
data: {
status: "ready",
brandVoiceCount: summary.brandVoices.count,
glossaryCount: summary.glossaryItems.count,
instructionCount: summary.instructions.count,
},
});
}
if (type === "provisioning.failed") {
console.error(`Provisioning failed: ${jobId} (${engineId})`, req.body.error);
await db.engines.update({
where: { engineId },
data: { status: "needs_configuration" },
});
}
});El middleware verifyWebhook es la única pieza que esta página no define. Todas las entregas van firmadas según la especificación Standard Webhooks: tres cabeceras, un HMAC sobre el cuerpo sin procesar y un secreto whsec_ generado la primera vez que envías un trabajo con un callback. Los callbacks de aprovisionamiento y de localización usan ese esquema sin cambios, así que está documentado una sola vez en verificación de firma de webhooks. Conecta el middleware antes de confiar en una carga útil: un cuerpo sin verificar es un cuerpo no autenticado.
Verifica antes de confiar en el cuerpo
Tu endpoint es una URL pública; cualquiera puede hacer POST a ella. Verifica la firma sobre el cuerpo sin procesar de la petición antes de actuar sobre cualquier carga útil: antes de marcar un motor como listo o de guardar los IDs que dice haber creado. El cómo —las cabeceras, el HMAC, el secreto whsec_— está en la página de verificación de firma.
Cuándo la entrega no es la herramienta adecuada#
El webhook es una comodidad push, no el sistema de registro. Hay dos casos en los que conviene usar otra cosa, y ambos están a un clic.
Si tu endpoint estaba caído cuando se entregó el resultado, la plataforma reintenta con la misma programación que usa cualquier webhook de Lingo, y el resultado no se queda atrapado en el callback. Los registros que ha creado la IA son la configuración real del motor; el resumen completed es un informe de trabajo que ya ha ocurrido en un motor real, no la única copia de ese trabajo. Así que un periodo de inactividad te cuesta una notificación, nunca el motor. La propia programación de reintentos está en la página de firma y entrega.
Y si lo que quieres es progreso en tiempo real mientras se configura el motor —un estado de rastreo y configuración en una interfaz, en lugar de un único callback a tu servidor cuando termina—, entonces necesitas el WebSocket del trabajo de aprovisionamiento, no el webhook. Envía una instantánea al conectar y eventos de progreso a medida que avanza la ejecución, y puedes conectarte en cualquier momento, incluso después de que el trabajo haya terminado.
