Я дал Cursor документацию по Webhook — и за полчаса получил Telegram→Slack ретранслятор — UnifyPort
Если вам нужны сообщения из Telegram в Slack, стандартный путь таков: создать бота через @BotFather, настроить Webhook или getUpdates-поллинг, распарсить вложенный объект Update Telegram, отформатировать для Incoming Webhook Slack и обработать лимиты. Это работает, но код привязан исключительно к Telegram. Объект Update имеет собственную структуру, бот видит только свои диалоги, а если позже вы захотите получать WhatsApp-сообщения в тот же Slack-канал — придётся интегрировать совершенно другой API с нуля.
Я пошёл другим путём. Открыл Cursor, вставил справочник Webhook UnifyPort и попросил собрать Telegram→Slack ретранслятор. Через тридцать минут у меня был Express-сервер, который проверяет подпись каждой доставки через HMAC-SHA256, форматирует входящие Telegram-сообщения с помощью Slack Block Kit и отправляет их в канал — примерно 45 строк кода. Тот же ретранслятор обрабатывает WhatsApp, LINE, TikTok, Zalo и X без каких-либо изменений, потому что структура входящего события одинакова для всех шести платформ.
Что получится в итоге
Node.js Express-сервер, который:
- Принимает события
message.receivedот унифицированного Webhook UnifyPort - Проверяет подпись каждой доставки через HMAC-SHA256 с вашим
signing_secret - Форматирует каждое сообщение в Slack Block Kit с тегом платформы, отправителем и временной меткой
- Отправляет в Slack-канал через Incoming Webhook
Время: около 30 минут. Понадобится рабочее пространство UnifyPort с подключённым Telegram-аккаунтом (QR-сканирование — регистрация бота не требуется) и URL Slack Incoming Webhook.
Почему не использовать Bot API напрямую?
Краткий обзор того, что требует путь через Telegram Bot API:
- Создать бота через @BotFather — вы получаете bot token, но бот — это отдельная сущность, не ваш личный аккаунт
- Выбрать способ доставки: поллинг
getUpdatesили Webhook — оба требуют настройки, специфичной для Telegram - Распарсить объект
Update: текст сообщения вupdate.message.text, отправитель вupdate.message.from.id, чат вupdate.message.chat.id— структура уникальна для Telegram - Обработать лимиты: 30 сообщений/секунду глобально, 20 сообщений/минуту в одну группу
- Отформатировать и переслать в Slack
Код работает — пока вам не понадобятся WhatsApp-сообщения в том же канале. Тогда вы интегрируете WhatsApp Cloud API отдельно, парсите другой Webhook-пейлоад и поддерживаете два кодовых пути, оба выдающих Slack-сообщение. Три платформы — три кодовых пути.
Webhook UnifyPort решает это. Каждая платформа доставляет одно и то же событие message.received — provider, from, text, message_id. Один обработчик обслуживает все шесть каналов.
Подготовка: передайте документацию в Cursor
Перед написанием промптов загрузите API-справочник в контекст Cursor. Используйте @Docs для добавления справочника Webhook UnifyPort — особенно:
- Структура пейлоада события
message.received - Заголовок
x-unifyport-signatureи описание проверки HMAC-SHA256 - Вызов создания эндпоинта
POST /v1/webhook-endpoints
Или вставьте эти секции прямо в чат. Главное: реальные имена полей на входе — корректный код на выходе. Без справочника Cursor придумает коллбэк telegram.onMessage() или обработчик bot.on('text'), которых в API UnifyPort не существует.
Claude Code, Windsurf и Copilot работают точно так же — файловый контекст, панель документации или вставка в чат. Инструмент вторичен; документация — главное.
Сборка
Первый промпт — скелет ретранслятора:
Используя документацию UnifyPort webhook, напиши Express-сервер с маршрутом POST /webhook. Когда event равен “message.received”, отправляй отформатированное сообщение на Slack incoming webhook по URL из SLACK_WEBHOOK_URL. Включи имя provider, отправителя и текст сообщения. Для всех событий возвращай 200.
Cursor читает справочник и генерирует:
import express from "express";
const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK_URL;
const app = express();
app.use(express.json());
app.post("/webhook", async (req, res) => {
const evt = req.body;
if (evt.event === "message.received") {
await fetch(SLACK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `*[${evt.provider}]* ${evt.from}: ${evt.text}`,
}),
});
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("listening on :3000"));
Пятнадцать строк обработчика. Поскольку структура события одинакова для всех шести платформ, парсинг, специфичный для Telegram, не нужен — evt.provider содержит "telegram", но структура идентична сообщению из WhatsApp или LINE.
Второй промпт — проверка подписи:
Каждая доставка включает заголовок x-unifyport-signature. Проверяй его через HMAC-SHA256, используя signing_secret из переменной окружения. HMAC должен вычисляться по сырому телу запроса — не по ресериализованному JSON. При неудаче возвращай 401. Используй timing-safe сравнение.
Cursor добавляет слой проверки:
import crypto from "crypto";
const SIGNING_SECRET = process.env.UNIFYPORT_SIGNING_SECRET;
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", SIGNING_SECRET)
.update(req.rawBody)
.digest("hex");
return sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Коллбэк verify захватывает сырой буфер до того, как Express распарсит JSON. Если бы Cursor использовал req.body и ресериализовал его, разница в пробелах привела бы к несовпадению HMAC. Промпт указал «сырое тело запроса», поэтому реализация получилась корректной без доработок.
Третий промпт — улучшенное форматирование Slack:
Измени Slack-сообщение на формат Block Kit. Отображай provider как тег, sender ID жирным. Добавь context-блок с timestamp и message_id.
Финальный server.js:
import express from "express";
import crypto from "crypto";
const SIGNING_SECRET = process.env.UNIFYPORT_SIGNING_SECRET;
const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK_URL;
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", 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" && SLACK_WEBHOOK) {
await fetch(SLACK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*[${evt.provider}]* from *${evt.from}*\n${evt.text}`,
},
},
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `${evt.message_id} · ${new Date(evt.timestamp * 1000).toISOString()}`,
},
],
},
],
}),
});
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("listening on :3000"));
Менее 50 строк. Проверка подписи, форматирование Slack Block Kit, пересылка всех входящих сообщений. Обработчик никогда не проверяет значение provider, потому что в этом нет необходимости.
Запустите и посмотрите, как приходит сообщение из Telegram
Подключите Telegram-аккаунт в рабочем пространстве UnifyPort — Telegram поддерживает QR-аутентификацию, сканируйте из приложения Telegram, и через несколько секунд всё готово. Без @BotFather, без bot token, без управления API-ключами. Подключается ваш реальный Telegram-аккаунт, а не отдельный бот.
Зарегистрируйте Webhook-эндпоинт, указывающий на ваш сервер, с subscribed_events: ["message.received"] и signing_secret. Запустите сервер, затем отправьте тестовое сообщение на ваш Telegram-аккаунт от другого пользователя. Событие приходит:
{
"event": "message.received",
"account_id": "acct_5Qm8nR",
"provider": "telegram",
"from": "user_7c3d9e",
"text": "Завтра в три всё ещё встречаемся?",
"timestamp": 1750521600,
"message_id": "tg_msg_2a6f4b"
}
Сервер проверяет подпись, форматирует Block Kit пейлоад и отправляет в Slack. Сообщение появляется в канале с тегом платформы, отправителем, временной меткой и полным текстом. Если проверка подписи возвращает 401 — убедитесь, что UNIFYPORT_SIGNING_SECRET совпадает со значением, установленным на Webhook-эндпоинте.
Архитектурный выигрыш: добавьте WhatsApp без изменения кода
Здесь ретранслятор расходится с Bot API-решением. Подключите WhatsApp-аккаунт в том же рабочем пространстве, подпишите тот же Webhook-эндпоинт. WhatsApp-сообщение приходит в точно такой же структуре:
{
"event": "message.received",
"account_id": "acct_3Xk1wL",
"provider": "whatsapp",
"from": "user_9b4e7a",
"text": "Счёт отправлен, подтвердите получение",
"timestamp": 1750521660,
"message_id": "wa_msg_8d2c5f"
}
Тот же обработчик. Тот же Slack-канал. Тег [whatsapp] отличает его от [telegram] в Slack, но кодовый путь идентичен. Для Telegram Bot API ретранслятора потребовалась бы отдельная интеграция — другой Webhook-пейлоад, другая логика парсинга, другая обработка ошибок. Этот ретранслятор обрабатывает LINE, TikTok, Zalo и X тем же способом, потому что структура события не меняется между платформами.
В этом разница между разработкой под API одной платформы и разработкой под нормализованный слой. Путь через Bot API привязывает ретранслятор к Telegram. Справочник Webhook UnifyPort даёт Cursor — или любому AI-инструменту для написания кода — достаточно простую поверхность, чтобы завершить работу с первой попытки, а результат работает для каждого канала, который вы подключите позже. Загрузите справочник API UnifyPort в ваш инструмент, вставьте схему message.received, и ретранслятор будет работать до обеда.