Вы создали задание Развёртывание и получили в ответ 202: ID движка и status: "in_progress". Теперь AI-агент в фоновом режиме обходит ваши источники и применяет к этому движку тональности бренда, элементы глоссария и инструкции. Это может занять и пару мгновений, и заметно больше времени — в зависимости от того, сколько ссылок ему нужно обойти. Можно держать WebSocket-соединение открытым и следить за процессом, но вряд ли вам захочется постоянно держать соединение открытым только ради того, чтобы узнать, когда агент закончит и что именно он создал.
Именно для этого и нужен webhook. Если при создании задания передать callbackUrl, Lingo отправит итоговый результат POST-запросом на этот URL сразу после завершения задания — вы узнаете, когда движок готов, и получите список того, что было создано. Если задание завершилось успешно, придёт provisioning.completed со сводкой по всем записям, созданным AI. Если задание завершилось ошибкой, придёт provisioning.failed с причиной. В любом случае ваш процесс настройки получит уведомление без дополнительных запросов.
На этой странице разобраны оба payload и то, как с ними работать. Доставка подписывается и повторяется при сбое — этот механизм общий с локализацией и описан на странице проверка подписи webhook, на которую мы ссылаемся везде, где она понадобится.
На этой странице
- Как работает доставка
- Payload завершённого задания
- Payload задания с ошибкой
- Обработка webhook
- Когда доставка — не лучший инструмент
Как работает доставка#
Задание Развёртывание завершается ровно один раз. В тот момент, когда оно достигает финального состояния — все источники обойдены и проанализированы либо выполнение остановлено, — результат доставляется на ваш callbackUrl одним POST. Группа Локализация разбивается на отдельные задания для каждой целевой локали, и каждое отправляет свой callback; у Развёртывание задание одно, значит и доставка одна.
Укажите адрес назначения через callbackUrl, когда создаёте задание. По сети передаются два типа payload, различающиеся полем type: provisioning.completed и provisioning.failed. В обоих указаны jobId и engineId, к которым они относятся, поэтому один обработчик может маршрутизировать их по type и обновлять нужную запись.
Только HTTPS
callbackUrl должен использовать HTTPS. URL с HTTP будет отклонён при создании задания — webhook подписывается, а передавать подписанный payload по незащищённому протоколу бессмысленно.
Корректно обрабатывайте неизвестные типы событий
Сейчас по сети передаются provisioning.completed и provisioning.failed. Но считайте этот набор открытым: обрабатывайте известные вам типы и игнорируйте остальные, чтобы новый тип события в будущем не сломал уже развёрнутый обработчик.
Payload завершённого задания#
Когда задание завершается, payload содержит summary — тот же список, который вы получили бы, запросив задание, только здесь он приходит сам, без опроса. В нём перечислены все тональности бренда, элементы глоссария и инструкции, которые AI создал в вашем движке, а также все сбои по отдельным элементам, возникшие по пути.
{
"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": []
}
}| Поле | Описание |
|---|---|
type | provisioning.completed |
jobId | Завершившееся задание Развёртывание (префикс pjb_) |
engineId | Движок, который оно настроило (префикс eng_) |
summary | Что AI создал в движке: количество и ID по каждому компоненту, а также сбои по отдельным элементам в errors |
summary — это тот же объект, который содержит само задание, а его значение поле за полем — что представляет собой каждый компонент, как элементы соотносятся с локалями, что попадает в errors — описано один раз на странице Что извлекает AI. Здесь достаточно понимать главное: payload завершённого задания передаёт вам ID всего, что создал агент, так что обработчик может сохранить их или показать в панели управления без повторного запроса задания.
Даже непустой массив errors приходит как completed.
Сбои по отдельным элементам не означают сбой всего задания. Если один источник не удалось обойти или одну запись не удалось создать, она попадёт в summary.errors, а всё остальное всё равно будет применено к движку — и payload останется provisioning.completed, а не provisioning.failed. Событие completed означает, что задание дошло до конца; смотрите errors, чтобы понять, что нужно исправить. Payload provisioning.failed отправляется, когда выполнение вообще не дало пригодного к использованию движка.
Payload задания с ошибкой#
Задание Развёртывание завершается ошибкой, когда в результате выполнения не удаётся получить ничего пригодного для работы — например, если не удалось обойти ни один источник и агенту просто нечего анализировать. В этом случае вы всё равно получите уведомление. Тип payload — provisioning.failed, и вместо сводки он содержит строку error:
{
"type": "provisioning.failed",
"jobId": "pjb_A1b2C3d4E5f6G7h8",
"engineId": "eng_X1y2Z3a4B5c6D7e8",
"error": "All sources failed to crawl. No content available for analysis."
}| Поле | Описание |
|---|---|
type | provisioning.failed |
jobId | Задание Развёртывание, завершившееся ошибкой |
engineId | Движок, который был создан, но остался без конфигурации |
error | Понятная человеку причина, по которой задание не удалось завершить |
Здесь у скептически настроенного читателя возникает вполне логичный вопрос: если задание завершилось ошибкой, я потерял и движок тоже? Нет. engineId в этом payload — тот же движок, который вы получили в 202: он по-прежнему существует, был создан в момент вызова, просто без конфигурации, которую добавил бы успешный запуск. Ошибка лишает вас только результатов извлечения, но не движка. Исправьте отправленные данные и попробуйте снова — или настройте движок вручную из панели управления. Если задание падает на этапе обхода, причина обычно в источниках — на странице Типы источников описано, какие источники действительно стоит указывать.
Обработка webhook#
Первая мысль скептически настроенного читателя здесь совершенно справедлива: мой обработчик делает реальную работу — пишет в базу данных, отправляет уведомление, обновляет панель управления — не будет ли он держать соединение открытым слишком долго и из-за этого webhook не получит тайм-аут?
Будет — поэтому не заставляйте Lingo ждать. Сначала верните 200, затем обрабатывайте. Сначала подтвердите получение, а уже потом выполняйте основную работу, после отправки ответа. Полный контракт доставки — почему подтверждать нужно сначала и какой график повторов включается, если этого не сделать, — описан на странице подпись и доставка; обработчик ниже показывает, как это выглядит для payload Развёртывание.
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" },
});
}
});Middleware verifyWebhook — единственная часть, которую эта страница не описывает. Каждая доставка подписывается по спецификации Standard Webhooks: три заголовка, HMAC по сырому телу запроса, секрет whsec_, который создаётся при первой отправке задания с callback. Callback'и Развёртывание и локализация используют эту схему без изменений, поэтому она описана один раз на странице проверка подписи webhook. Подключайте middleware до того, как начнёте доверять payload: непроверенное тело запроса — это неаутентифицированное тело запроса.
Проверяйте подпись, прежде чем доверять телу запроса
Ваш endpoint — публичный URL; кто угодно может отправить на него POST. Проверяйте подпись по сырому телу запроса, прежде чем что-либо делать с payload, — до того, как пометите движок как готовый или сохраните ID, которые он якобы создал. Как это устроено — заголовки, HMAC, секрет whsec_ — описано на странице проверка подписи.
Когда доставка — не лучший инструмент#
Webhook — это удобный push-механизм, а не источник истины. В двух случаях лучше использовать другой инструмент, и оба — в одном клике отсюда.
Если ваш endpoint был недоступен в момент доставки результата, платформа повторит попытку по тому же графику, что и для всех webhook в Lingo, — и результат не остаётся запертым внутри callback. Записи, созданные AI, — это реальная конфигурация движка; completed summary — это отчёт о работе, которая уже произошла на настоящем движке, а не её единственная копия. Поэтому даже длительный простой лишит вас уведомления, но не движка. Сам график повторов описан на странице подпись и доставка.
А если вам нужен живой прогресс, пока движок настраивается, — например, статус в UI с этапами обхода и настройки, а не один callback на сервер по завершении, — тогда вам нужен WebSocket задания Развёртывание, а не webhook. Он отдаёт снимок состояния при подключении и события прогресса по мере выполнения, и подключиться можно в любой момент, даже после завершения задания.
