← Все статьи
Руководство

Я направил ИИ-агента для кодинга на документацию вебхуков — и он собрал рабочего бота-автоответчика — UnifyPort

Знакомый ход. В списке дел висит интеграция с мессенджером, и вместо того чтобы читать документацию от корки до корки, вы открываете Cursor — или Claude Code, или Windsurf — и начинаете писать промпты. В 2026 году большинство из нас именно так и начинает проект. Настоящий вопрос не в том, будете ли вы «вайб-кодить» с ИИ. Он в том, даёт ли API, с которым вы работаете, агенту достаточно, чтобы чисто довести дело до конца.

Это билд-лог. К концу у вас будет рабочий бот-автоответчик: приёмник вебхуков, который проверяет подпись на каждом входящем сообщении и отвечает через единственный эндпойнт отправки — примерно день работы, и бóльшую часть делает агент. Быстро получается потому, что вебхук UnifyPort нормализован. Нужно выучить одну форму события и вызвать один эндпойнт — агенту нечего угадывать.

Подготовка: направьте агента на документацию

Самый важный шаг — тот, который все пропускают: занесите справочник API в контекст агента до того, как попросите код. Три рабочих подхода на сегодня:

  • Вставьте страницу. Скопируйте форму события message.received и раздел отправки POST /v1/messages прямо в чат.
  • Прикрепите документацию. @Docs в Cursor, файловый контекст Claude Code, панель документации Windsurf — направьте их на справочник UnifyPort, чтобы агент читал его по запросу.
  • Используйте коннектор документации/MCP, если ваш инструмент его поддерживает, чтобы агент сам подтягивал справочник.

Отдача конкретна. Агент, который действительно прочитал имена событий, не выдумает обработчик onMessage() или несуществующий SDK sendText(). Он пишет под message.received и POST /v1/messages, потому что именно это у него перед глазами. Мусорный контекст на входе — галлюцинированные эндпойнты на выходе, так что потратьте эти две минуты и заведите настоящие.

Сборка, промпт за промптом

Первый промпт — приёмник:

По документации вебхуков UnifyPort, что я дал, напиши Express-сервер с маршрутом POST /webhook, принимающим стандартные события. Пока что: когда event равен message.received, логируй from и text.

Поскольку форма события фиксирована, обработчик — это ветвление по одному полю:

import express from "express";

const app = express();
app.use(express.json());

app.post("/webhook", (req, res) => {
  const evt = req.body;
  if (evt.event === "message.received") {
    console.log(`${evt.provider} ${evt.from}: ${evt.text}`);
  }
  res.sendStatus(200);
});

app.listen(3000, () => console.log("listening on :3000"));

Второй промпт — проверка подписи. Этот шаг небрежная сборка пропускает, а агент, прочитавший документацию, — нет:

Каждая доставка подписана HMAC-SHA256 с signing_secret вебхука. Проверь её, прежде чем доверять телу. Используй сырое тело запроса и сравнение, устойчивое по времени.

Здесь агент обязан попасть в одно: HMAC должен считаться по сырым байтам, а не по повторно сериализованному JSON. С подсказкой «используй сырое тело запроса» хороший агент подключает хук verify у express.json, чтобы поймать rawBody. Если забудет — это ваша единственная точка ревью, и вы заметите её при первой же реальной доставке с ответом 401.

Третий промпт — ответ через эндпойнт отправки:

Когда приходит message.received, ответь отправителю через POST /v1/messages, используя account_id и from из события. Авторизация — Bearer API-ключ из окружения.

Вот и весь бот. Полный файл, на котором останавливается агент:

import express from "express";
import crypto from "crypto";

const { UNIFYPORT_API_KEY, UNIFYPORT_SIGNING_SECRET } = process.env;

const app = express();
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));

function isValid(req) {
  const sig = req.get("x-unifyport-signature") || "";
  const expected = crypto
    .createHmac("sha256", UNIFYPORT_SIGNING_SECRET)
    .update(req.rawBody)
    .digest("hex");
  return sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

app.post("/webhook", async (req, res) => {
  if (!isValid(req)) return res.sendStatus(401);

  const evt = req.body;
  if (evt.event === "message.received") {
    await fetch("https://api.unifyport.ai/v1/messages", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${UNIFYPORT_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        account_id: evt.account_id,
        to: evt.from,
        text: "Спасибо! Мы получили ваше сообщение — оператор скоро ответит.",
      }),
    });
  }
  res.sendStatus(200);
});

app.listen(3000, () => console.log("listening on :3000"));

Запустите и посмотрите, как приходит сообщение

Подключите один аккаунт (Telegram проще всего для теста), зарегистрируйте эндпойнт вебхука на ваш сервер с signing_secret и subscribed_events: ["message.received"], затем отправьте боту личное сообщение. Событие приходит таким:

{
  "event": "message.received",
  "account_id": "acct_8Q2vK",
  "provider": "telegram",
  "from": "user_3f9c1a",
  "text": "Здравствуйте, вы работаете по выходным?",
  "timestamp": 1749427200,
  "message_id": "tg_msg_5d2b7e"
}

Ваш сервер проверяет подпись, вызывает POST /v1/messages, и ответ появляется в чате. Если проверка подписи не прошла, вы получите 401 — это та самая ошибка с rawBody сверху. Исправьте один раз, и цикл станет надёжным.

Расширьте

Вот где нормализованный вебхук окупается. Добавить вторую платформу — это не вторая интеграция, а ноль нового кода обработчика. Подключите аккаунт WhatsApp, LINE или Zalo, подпишите тот же вебхук — и сработает ровно та же ветка message.received, потому что форма события не меняется между провайдерами. Поле provider подскажет, откуда пришло, а остальной код на это даже не смотрит.

Хотите, чтобы бот действительно отвечал, а не просто подтверждал получение? Это ещё один промпт: «перед ответом отправь text в LLM и используй его ответ как тело сообщения». Вызов отправки агент уже написал — вы лишь меняете одну строку на ответ модели.

Урок этого дня не в том, что «ИИ пишет бота за вас». В том, что ИИ-агент хорош ровно настолько, насколько хороша поверхность, на которую вы его навели. Нормализованный вебхук со стабильными именами событий и одним эндпойнтом отправки — именно та поверхность, которую агент доводит до конца чисто: одна форма для изучения, ничего выдумывать не надо. Направьте инструмент на UnifyPort v1 API, вставьте форму message.received — и до конца дня у вас будет бот, который проверяет подпись и отвечает, а дальше отпустите его на остальные пять каналов.