Devin DesktopにWebhookドキュメントを渡して20分でLINE受信ハンドラーを構築した話
Devin Desktop——2026年初にWindsurfからリブランドされ、SWE-1.6モデルが950トークン/秒を叩き出す——は今、周りの開発者が最も話題にしているAIコーディングツールです。使い方はシンプル:リファレンスドキュメントを渡して、欲しいものを説明し、動くまでイテレーションする。このワークフローがwebhook APIに対してどこまで通用するか試してみました。
タスク:LINEのメッセージをwebhookイベントとして受信し、構造化ログに出力するPythonサーバー——小規模チームがメッセージキューやチケットシステムの前段にデプロイするようなハンドラー。LINE公式アカウント不要、Messaging API認証情報不要、チャネルアクセストークン管理不要。インバウンドメッセージが正規化されたJSONで届けばそれで十分です。
完成するもの
FastAPIサーバー(約40行):
- UnifyPort統一webhookの
message.receivedイベントを受信 signing_secretでHMAC-SHA256署名を検証- 各メッセージを構造化JSONでログ出力——provider、sender、text、timestamp
- すべてのイベントに200を返し、署名不正なら401
所要時間:約20分。UnifyPortワークスペースにLINEアカウントを接続(LINEアプリでQRスキャン)し、Python 3.10+が必要です。
なぜLINE Messaging APIを直接使わないのか
公式ルート:
- LINE公式アカウントを作成——LINE Business IDが必要、企業または本人確認済み個人の認証が必要
- LINE Developersコンソールでmessaging APIを有効化——チャネル設定、チャネルアクセストークン生成、webhook URL設定
- LINEのwebhook payloadを解析:イベント構造は
{"events": [{"type": "message", "message": {"type": "text", "text": "..."}, "source": {"userId": "U..."}}]}——ネストされたLINE固有のフォーマット - 署名を検証:チャネルシークレットで
x-line-signatureを検証——LINEはBase64エンコードのHMAC-SHA256を使用(hexではない) - リプライトークンを処理:各イベントに有効期限30秒の
replyTokenが付属——ウィンドウを逃すと返信はサイレントに失敗
コードは動きますが、LINE専用です。webhook payloadフォーマット、署名形式、リプライトークン機構——どれもWhatsAppやTelegramに流用できません。2つ目のプラットフォームを追加するには、ゼロから別の統合を構築する必要があります。
UnifyPortの非公式インターフェースは個人のLINEアカウントを接続——QRスキャン、公式アカウント不要——すべてのインバウンドメッセージを正規化されたmessage.receivedイベントとして配信します。WhatsApp、Telegram、TikTok、Zalo、Xと完全に同じフォーマット。一つのハンドラーで6プラットフォームをカバーします。
セットアップ:Devin DesktopにAPIリファレンスを渡す
Devin Desktopを開き、新しいセッションを作成。最初のプロンプトの前にコンテキストを提供:
- UnifyPort webhookドキュメントから
message.receivedイベントpayloadを貼り付け:{ "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ヘッダー、signing_secretを使って生のリクエストボディからhexダイジェストを計算
Devinはコンテキストとしてペーストされたドキュメントをインデックスします。SWE-1.6 Fastが950 tok/sで処理するため、ドキュメントの消化は数秒。Cursor、Claude Code、Copilotも同じ方法で動きます——ペーストまたは添付。ツールは交換可能、ドキュメントが本質です。
ビルドプロセス
最初のプロンプト——ハンドラーの骨格:
先ほど貼り付けたUnifyPort webhookリファレンスを使って、FastAPIサーバーを書いてください。POST /webhookルート。イベントが”message.received”の場合、Pythonのloggingモジュールで構造化JSONとしてprovider、from、text、timestampをログ出力。すべてのイベントに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専用のimportなし、linebot SDKなし、チャネルアクセストークンなし。イベント構造が十分フラットなので、追加のパースは不要です。
2番目のプロンプト——署名検証:
HMAC-SHA256署名検証を追加。ヘッダーはx-unifyport-signature、シークレットは環境変数UNIFYPORT_SIGNING_SECRETから取得、HMACは生のリクエストボディバイトに対して計算——再シリアライズしたJSONではない。検証失敗は401を返す。タイミングセーフな比較を使用。
3番目のプロンプト——統合と整理:
署名検証を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メッセージの着信を確認
UnifyPortワークスペースでLINEアカウントを接続——LINEはQRコード認証に対応しているので、LINEアプリでスキャンすれば数秒で接続完了。LINE公式アカウント不要、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——をゼロから構築する必要があります。このハンドラーは6プラットフォームに対応します。イベント形状が変わらないからです。
日本市場ではLINEが圧倒的ですが、インバウンドのカスタマーサポートや越境ECではWhatsAppやTelegramからのメッセージも届きます。プラットフォームごとに別の統合を維持するのではなく、一つのハンドラーですべてカバーする——Devin DesktopにUnifyPort APIリファレンスを渡して、ハンドラーを記述し、動くまでイテレーション。ツールはDevinでもCursorでもClaude Codeでも——リファレンスが同じなら、ハンドラーは40行以内で完成します。