← 全記事
チュートリアル

GitHub CopilotでTikTok DM Webhookレシーバーを作る

TikTokのDMをサポート窓口に流したいなら、AIに未知のpayloadを推測させるのではなく、最初に本物の契約を渡します。UnifyPortはTikTokの入信メッセージを、WhatsApp、Telegram、LINE、Zalo、Xと同じ標準 message.received イベントとして配信し、raw bodyをHMAC-SHA256で署名します。

要点

  • TikTok公式ドキュメントでは、Direct MessagesはData Portabilityのscopeやdata typeとして説明されています。一般的なライブサポート用Webhookとは別物です。
  • CopilotにはUnifyPortの実フィールドを渡します:POST /v1/webhook-endpointssubscribed_eventssigning_secretX-Device-TimestampX-Device-Signature
  • 署名検証はJSON parse前のraw bodyで行います。再シリアライズしたJSONでは署名が一致しません。
  • 日本やタイのチームでLINE連携が重要でも、同じレシーバーにLINEを後から足せるのが統一Webhookの利点です。

背景を確認したい場合は、まず TikTok DM APIに公式の汎用エンドポイントがない理由 を読んでください。ライブ取り込みが必要だと分かっている場合は、TikTok Data PortabilityとライブDMの違い が実装前の整理になります。

完成するデモ

作るのは小さなNode.jsサービスです。/webhook routeでUnifyPortイベントを受け、X-Device-TimestampX-Device-Signature を検証し、message.received だけを保存して 200 を返します。

実装中は webhook delivery and signature verification を開いておきます。ここに署名文字列、ヘッダー、retry、idempotencyが記載されています。

Copilotへの最初のprompt

Build a minimal Express service for a UnifyPort webhook receiver.
Use express.raw({ type: 'application/json' }). Verify X-Device-Signature as hex HMAC-SHA256 over X-Device-Timestamp + '.' + raw request body using WEBHOOK_SIGNING_SECRET.
Only process event.type === 'message.received'. Store provider, account_id, conversation.id, sender.id, message.id, message.text, message.direction, and occurred_at.
Return 200 after storing; return 401 on invalid signature.

期待する中心部分は次の形です。

import crypto from 'crypto';
import express from 'express';

const app = express();
const secret = process.env.WEBHOOK_SIGNING_SECRET;
const queue = [];

function verifySignature(req) {
  const timestamp = req.get('X-Device-Timestamp') || '';
  const signature = req.get('X-Device-Signature') || '';
  const expected = crypto.createHmac('sha256', secret)
    .update(timestamp + '.')
    .update(req.body)
    .digest('hex');
  return signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifySignature(req)) return res.status(401).end();
  const event = JSON.parse(req.body.toString('utf8'));
  if (event.type !== 'message.received') return res.status(200).end();
  queue.push({
    provider: event.provider,
    account_id: event.account_id,
    conversation_id: event.data.conversation.id,
    sender_id: event.data.sender.id,
    message_id: event.data.message.id,
    text: event.data.message.text || '',
    direction: event.data.message.direction,
    occurred_at: event.occurred_at,
  });
  res.status(200).end();
});

Webhook endpointを登録する

HTTPSで公開したあと、POST /v1/webhook-endpoints を作成します。subscribed_eventsmessage.received に絞り、signing_secret で署名を有効にします。

curl -X POST https://api.unifyport.ai/v1/webhook-endpoints \
  -H "X-Api-Key: dk_live_example" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://inbox.example.com/webhook",
  "status": "active",
  "subscribed_events": ["message.received"],
  "signing_secret": "whsec_6f5b1c9d4e7a2b8c"
}'

AI coding workflow全体を見たい場合は、AI coding agentで自動返信botを作るチュートリアル も参考になります。

実行後に追加するもの

次のpromptでは、X-Device-Event-Id による重複排除、raw event保存、data.message.direction === 'inbound' のみをキューへ送る処理を追加させます。Slack通知、CRM upsert、AI分類はその後で構いません。LINEを追加する場合も同じイベント形状を使えます。

制限

公式の投稿、ログイン、研究、データエクスポートが必要ならTikTok公式APIを使います。UnifyPortの非公式インターフェースは、既存アカウントのライブ入信メッセージを署名付きイベントとして受けるためのものです。

FAQ

Copilotだけでinbox全体を作れますか?

レシーバー、テスト、キュー処理の下書きは作れます。ただし署名検証、secret管理、保存設計は必ずレビューしてください。

Data PortabilityはライブDMですか?

いいえ。公式ドキュメントはエクスポートのscopeとデータ型を説明しています。サポート運用にはライブイベントストリームが必要です。

どのイベントを購読しますか?

入信メッセージなら message.received です。全イベント収集を作る場合だけ ["*"] を検討します。

Sources checked on 2026-08-29

UnifyPort API

メッセージ連携を安定したプロダクトパイプラインへ。

まずは 1 つの API で送信を始め、標準イベントですべての inbound メッセージを業務システムへ戻しましょう。