Скормил 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 строк):
- Принимает события
message.receivedот унифицированного вебхука UnifyPort - Проверяет каждую доставку с помощью HMAC-SHA256 по
signing_secret - Логирует каждое сообщение в структурированный JSON — provider, sender, text, timestamp
- Возвращает 200 для всех событий, 401 при невалидной подписи
Время: около 20 минут. Нужен воркспейс UnifyPort с подключённым LINE-аккаунтом (сканирование QR-кода из приложения LINE) и Python 3.10+.
Почему не LINE Messaging API напрямую?
Официальный путь:
- Создать LINE Official Account — нужен LINE Business ID, который требует верификации компании или физлица
- Включить Messaging API в консоли LINE Developers — настроить канал, сгенерировать channel access token, указать webhook URL
- Парсить webhook payload LINE: события приходят как
{"events": [{"type": "message", "message": {"type": "text", "text": "..."}, "source": {"userId": "U..."}}]}— вложенная, специфичная для LINE структура - Проверить подпись с помощью channel secret через
x-line-signature— LINE использует Base64-кодированный HMAC-SHA256, не hex - Обработать 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, создаём новую сессию. Перед первым промптом даём контекст:
- Вставляем payload события
message.receivedиз документации UnifyPort:{ "event": "message.received", "account_id": "acct_7kQnWx", "provider": "line", "from": "user_a3f82c", "text": "Можно перенести на пятницу?", "timestamp": 1751270400, "message_id": "line_msg_5e9d21" } - Вставляем раздел 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 строк.