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

Я попросил 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:

  1. Принимает события message.received от единого webhook UnifyPort
  2. Проверяет каждую доставку через HMAC-SHA256 с signing_secret
  3. Записывает каждое сообщение X в файл messages.jsonl
  4. Отправляет уведомление в 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 на этот интерфейс — одна схема событий, одна схема подписи — и к обеду у вас будет работающий слушатель с проверкой, логированием и уведомлениями. А потом добавьте все остальные каналы, не трогая обработчик.