Я попросил Claude Code собрать мониторинг DM и упоминаний в X — без единого обращения к API X — UnifyPort
У API X нет бесплатного тарифа. Чтение одного DM стоит $0,015. Чтение поста — $0,005. Если вы команда из двух человек и просто хотите знать, когда кто-то пишет вам в DM или упоминает вас в X, официальная платформа разработчика требует пополнить баланс, создать проект, дождаться одобрения и начать платить за каждый запрос — ещё до того, как вы обработали хоть одно событие.
Я всё это пропустил. Открыл Claude Code, вставил в контекст документацию webhook UnifyPort и попросил собрать мониторинг DM и упоминаний X на Node.js. Через сорок минут у меня был Express-сервер, который проверяет подпись каждой доставки, пишет события в структурированный лог и отправляет уведомления в Discord-канал. Ни аккаунта разработчика X, ни поштучной тарификации, ни очереди на одобрение.
Что у вас будет в итоге
Express-сервер на Node.js:
- Принимает события
message.receivedот единого webhook UnifyPort - Проверяет каждую доставку через HMAC-SHA256 с
signing_secret - Записывает каждое сообщение X в файл
messages.jsonl - Отправляет уведомление в Discord-канал через webhook
Время: меньше часа. Понадобится рабочее пространство UnifyPort с подключённым аккаунтом X через расширение UnifyPort Exporter — импорт сессии в один клик, без учётных данных разработчика — и URL вебхука Discord.
Подготовка: загрузите документацию в контекст Claude Code
Перед тем как давать промпт, передайте API-документацию агенту. Claude Code читает файлы напрямую, поэтому создайте unifyport-reference.md в корне проекта:
- Структура payload события
message.received - Заголовок
x-unifyport-signatureи описание проверки HMAC-SHA256 - Вызов создания
POST /v1/webhook-endpoints
Можно вставить эти разделы прямо в чат. Суть: настоящие имена полей на входе — корректный код на выходе. Без документации агент выдумает SDK для X или несуществующий тип события dm.received.
Cursor, Windsurf, Copilot работают так же — используйте @Docs, панель документации или вставьте документацию в контекст. Инструмент вторичен; документация — главное.
Сборка по промптам
Первый промпт — скелет:
Прочитай unifyport-reference.md. Напиши Express-сервер в server.js с маршрутом POST /webhook. Когда event равен “message.received”, выведи provider, from и text. На все запросы возвращай 200.
Claude Code выдаёт:
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"));
Шесть строк обработчика. Структура событий одинакова для всех шести платформ, поэтому никакого парсинга, специфичного для X — evt.provider показывает "x", но структура идентична сообщению из WhatsApp или Telegram.
Второй промпт — проверка подписи:
Каждая доставка содержит заголовок x-unifyport-signature. Проверь его через HMAC-SHA256 с signing_secret из переменной окружения. HMAC должен вычисляться по сырому телу запроса — не по пересериализованному JSON. При неудаче возвращай 401. Используй time-safe сравнение.
Claude Code добавляет слой проверки:
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 — если бы агент использовал req.body и пересериализовал в JSON, HMAC никогда бы не совпал. Промпт указал «сырое тело запроса», и агент справился с первой попытки.
Третий промпт — логирование и уведомления в Discord:
Добавь две функции: (1) дописывай каждое событие message.received в messages.jsonl — один JSON-объект на строку с timestamp, provider, from, text и message_id. (2) Отправляй однострочное уведомление на Discord webhook URL из переменной окружения DISCORD_WEBHOOK_URL.
Полный server.js:
import express from "express";
import crypto from "crypto";
import { appendFileSync } from "fs";
const SIGNING_SECRET = process.env.UNIFYPORT_SIGNING_SECRET;
const DISCORD_WEBHOOK = process.env.DISCORD_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") {
const record = JSON.stringify({
timestamp: evt.timestamp,
provider: evt.provider,
from: evt.from,
text: evt.text,
message_id: evt.message_id,
});
appendFileSync("messages.jsonl", record + "\n");
if (DISCORD_WEBHOOK) {
await fetch(DISCORD_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: `**[${evt.provider}]** ${evt.from}: ${evt.text}`,
}),
});
}
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("listening on :3000"));
Менее 50 строк. Проверка подписи, лог на диск, пересылка в Discord — всё на месте. Обработчик не ветвится по provider, потому что структура событий одинакова — неважно, пришло сообщение из X, WhatsApp или другой платформы.
Запуск: смотрим, как приходит сообщение X
Подключите аккаунт X в панели UnifyPort. Для X используется импорт сессии через расширение UnifyPort Exporter — нажмите на иконку расширения, авторизуйтесь, и аккаунт подключён за секунды. Без портала разработчика, без заявки на API-ключ, без очереди на одобрение.
Зарегистрируйте webhook-эндпоинт, указывающий на ваш сервер, с subscribed_events: ["message.received"] и signing_secret. Запустите сервер, затем отправьте тестовое DM на ваш аккаунт X с другого аккаунта. Событие приходит:
{
"event": "message.received",
"account_id": "acct_7Kp3mR",
"provider": "x",
"from": "user_9d4f2b",
"text": "Hey, are you still taking freelance projects?",
"timestamp": 1750521600,
"message_id": "x_msg_8c1e5a"
}
Сервер проверяет подпись, дописывает запись в messages.jsonl и отправляет уведомление в Discord. Если проверка подписи не прошла — 401 — проверьте, что UNIFYPORT_SIGNING_SECRET совпадает со значением, установленным на webhook-эндпоинте.
Расширение: добавляем Telegram без изменения кода
Подключите аккаунт Telegram в том же рабочем пространстве, подпишите тот же webhook-эндпоинт. Сообщение Telegram приходит в точно такой же структуре — другое поле provider, идентичная схема:
{
"event": "message.received",
"account_id": "acct_2Xn5wL",
"provider": "telegram",
"from": "user_4a7c8d",
"text": "Can you hop on a call tomorrow?",
"timestamp": 1750521660,
"message_id": "tg_msg_3b9f1e"
}
Нового обработчика не нужно. Парсинга для Telegram не нужно. В messages.jsonl теперь оба — события X и Telegram — в единой схеме, а Discord показывает уведомления из обоих потоков. Добавление WhatsApp, LINE, Zalo или TikTok — тот же процесс: подключите аккаунт, и уже написанный обработчик обработает всё.
Разница с прямым использованием API X — не только в стоимости, а в самом доступе. X требует одобренный аккаунт разработчика, пополненный проект и обязательство платить за каждое чтение. Путь импорта сессии UnifyPort подключает ваш личный аккаунт X через браузер и нормализует каждое DM и упоминание в ту же структуру событий, что и остальные пять каналов. Направьте Claude Code на этот интерфейс — одна схема событий, одна схема подписи — и к обеду у вас будет работающий слушатель с проверкой, логированием и уведомлениями. А потом добавьте все остальные каналы, не трогая обработчик.