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

Скормил Devin Desktop документацию по вебхукам — через 20 минут был готов обработчик входящих LINE-сообщений

Devin Desktop — ребрендинг Windsurf, произошедший в начале 2026 года, с моделью SWE-1.6, выдающей 950 токенов в секунду, — это AI-инструмент для кодирования, о котором сейчас говорят больше всего. Суть простая: передать справочную документацию, описать задачу, итерировать до рабочего состояния. Я решил проверить, как далеко этот рабочий процесс заходит, когда вместо руководства по фреймворку подаётся webhook API.

Задача: написать Python-сервер, принимающий LINE-сообщения через webhook-события и записывающий их в структурированный лог — такой обработчик, который небольшая команда разместит перед очередью или тикет-системой. Без LINE Official Account, без Messaging API, без управления channel access token. Только входящие сообщения в виде нормализованного JSON.

Что получится в итоге

FastAPI-сервер (около 40 строк):

  1. Принимает события message.received от унифицированного вебхука UnifyPort
  2. Проверяет каждую доставку с помощью HMAC-SHA256 по signing_secret
  3. Логирует каждое сообщение в структурированный JSON — provider, sender, text, timestamp
  4. Возвращает 200 для всех событий, 401 при невалидной подписи

Время: около 20 минут. Нужен воркспейс UnifyPort с подключённым LINE-аккаунтом (сканирование QR-кода из приложения LINE) и Python 3.10+.

Почему не LINE Messaging API напрямую?

Официальный путь:

  1. Создать LINE Official Account — нужен LINE Business ID, который требует верификации компании или физлица
  2. Включить Messaging API в консоли LINE Developers — настроить канал, сгенерировать channel access token, указать webhook URL
  3. Парсить webhook payload LINE: события приходят как {"events": [{"type": "message", "message": {"type": "text", "text": "..."}, "source": {"userId": "U..."}}]} — вложенная, специфичная для LINE структура
  4. Проверить подпись с помощью channel secret через x-line-signature — LINE использует Base64-кодированный HMAC-SHA256, не hex
  5. Обработать reply token: к каждому событию прилагается replyToken со сроком жизни 30 секунд — пропустили окно, и ответ молча не доставляется

Код работает, но только для LINE. Формат webhook payload, формат подписи, механизм reply token — ничего из этого не переносится на WhatsApp или Telegram. Второй мессенджер — это вторая интеграция с нуля.

Неофициальный интерфейс UnifyPort подключает обычный LINE-аккаунт — QR-сканирование, без Official Account — и доставляет каждое входящее сообщение как нормализованное событие message.received. Та же структура, что и для WhatsApp, Telegram, TikTok, Zalo и X. Один обработчик на шесть платформ.

Подготовка: передаём API-справочник в Devin Desktop

Открываем Devin Desktop, создаём новую сессию. Перед первым промптом даём контекст:

  1. Вставляем payload события message.received из документации UnifyPort:
    {
      "event": "message.received",
      "account_id": "acct_7kQnWx",
      "provider": "line",
      "from": "user_a3f82c",
      "text": "Можно перенести на пятницу?",
      "timestamp": 1751270400,
      "message_id": "line_msg_5e9d21"
    }
  2. Вставляем раздел HMAC-SHA256 — заголовок x-unifyport-signature, hex-дайджест от тела запроса с использованием signing_secret

Devin индексирует вставленный контент как часть сессии. SWE-1.6 Fast обрабатывает со скоростью 950 ток/с — документация усваивается за секунды. Cursor, Claude Code, Copilot работают точно так же — вставка или прикрепление. Инструмент взаимозаменяем; документация — вот что важно.

Процесс сборки

Первый промпт — скелет обработчика:

На основе документации UnifyPort webhook, которую я только что вставил, напиши FastAPI-сервер с маршрутом POST /webhook. Когда событие — “message.received”, логируй provider, from, text и timestamp как структурированный JSON через модуль logging Python. Возвращай 200 для всех событий. Запускай через uvicorn.

Devin читает контекст и генерирует:

from fastapi import FastAPI, Request
import logging, json

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("inbound")

app = FastAPI()

@app.post("/webhook")
async def webhook(request: Request):
    evt = await request.json()
    if evt.get("event") == "message.received":
        logger.info(json.dumps({
            "provider": evt["provider"],
            "from": evt["from"],
            "text": evt["text"],
            "timestamp": evt["timestamp"],
            "message_id": evt["message_id"],
        }))
    return {"status": "ok"}

12 строк обработчика. Никаких LINE-специфичных импортов, никакого linebot SDK, никакого channel access token. Структура события достаточно плоская — парсить нечего.

Второй промпт — проверка подписи:

Добавь HMAC-SHA256 проверку подписи. Заголовок — x-unifyport-signature, секрет берётся из переменной окружения UNIFYPORT_SIGNING_SECRET, HMAC вычисляется по сырым байтам тела запроса — не по ресериализованному JSON. При неудачной проверке — 401. Используй timing-safe сравнение.

Третий промпт — объединение и финализация:

Встрой проверку подписи в обработчик webhook. Парси body как JSON только после прохождения проверки. Добавь GET /health. Добавь блок uvicorn.run на порт 8000.

Итоговый server.py:

from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib, os, json, logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("inbound")

SIGNING_SECRET = os.environ["UNIFYPORT_SIGNING_SECRET"]

app = FastAPI()

@app.get("/health")
async def health():
    return {"status": "ok"}

@app.post("/webhook")
async def webhook(request: Request):
    body = await request.body()
    sig = request.headers.get("x-unifyport-signature", "")
    expected = hmac.new(
        SIGNING_SECRET.encode(), body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(sig, expected):
        raise HTTPException(status_code=401, detail="invalid signature")

    evt = json.loads(body)
    if evt.get("event") == "message.received":
        logger.info(json.dumps({
            "provider": evt["provider"],
            "from": evt["from"],
            "text": evt["text"],
            "timestamp": evt["timestamp"],
            "message_id": evt["message_id"],
        }))
    return {"status": "ok"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Менее 40 строк. Подпись проверена, логирование структурировано, готово к деплою за любым реверс-прокси.

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

Подключаем LINE-аккаунт в воркспейсе UnifyPort — LINE поддерживает QR-авторизацию, сканируем из приложения LINE, и аккаунт подключён за секунды. Без LINE Official Account, без Business ID, без настройки канала Messaging API.

Регистрируем webhook-эндпоинт, указывающий на ваш сервер, с subscribed_events: ["message.received"] и signing_secret. Запускаем:

UNIFYPORT_SIGNING_SECRET=your_secret python server.py

Просим друга отправить тестовое сообщение в ваш LINE. Событие приходит:

{
  "event": "message.received",
  "account_id": "acct_3Xk1wL",
  "provider": "line",
  "from": "user_a3f82c",
  "text": "Можно перенести на пятницу?",
  "timestamp": 1751270400,
  "message_id": "line_msg_5e9d21"
}

Сервер проверяет подпись и выводит структурированный JSON в лог. Если проверка подписи возвращает 401 — убедитесь, что UNIFYPORT_SIGNING_SECRET совпадает со значением, установленным на webhook-эндпоинте.

Добавляем WhatsApp без изменения кода

Подключаем WhatsApp-аккаунт в том же воркспейсе, подписываем тот же webhook-эндпоинт. WhatsApp-сообщение приходит в том же формате:

{
  "event": "message.received",
  "account_id": "acct_7kQnWx",
  "provider": "whatsapp",
  "from": "user_d4f29a",
  "text": "Заказ подтверждён, отправка в понедельник",
  "timestamp": 1751270460,
  "message_id": "wa_msg_8b3e71"
}

Тот же обработчик. Тот же структурированный лог. Поле provider меняется с "line" на "whatsapp", а путь выполнения кода остаётся идентичным. Обработчик LINE Messaging API потребовал бы полностью отдельной интеграции с WhatsApp — другой webhook payload, другой формат подписи, другой SDK. Этот обработчик обслуживает шесть платформ, потому что структура события не меняется.

Вот весь рабочий процесс: вставляем API-справочник UnifyPort в Devin Desktop, описываем обработчик, итерируем до рабочего состояния. Инструмент пишет код; нормализованный вебхук делает код универсальным для всех каналов. Замените Devin на Cursor, Claude Code или Copilot — справочник тот же, обработчик укладывается в 40 строк.