← All posts
Tutorial

Build a TikTok DM Webhook Receiver with GitHub Copilot and UnifyPort

GitHub Copilot can help you scaffold a TikTok DM webhook receiver if you give it the real webhook contract first. The important part is not the AI tool; it is the source material: UnifyPort delivers TikTok inbound messages as the same signed message.received envelope used for WhatsApp, Telegram, LINE, Zalo, and X.

Key takeaways

  • TikTok’s official developer docs expose direct-message data through Data Portability scopes and data-type exports, not as a general live customer-service webhook.
  • Build the receiver from the UnifyPort docs: POST /v1/webhook-endpoints, subscribed_events, signing_secret, X-Device-Signature, and the message.received payload.
  • Ask Copilot to verify the raw body before JSON parsing. Re-serializing JSON changes the bytes and breaks HMAC-SHA256 verification.
  • Store the event first, then route it to Slack, a CRM, or an AI triage worker.

If you are still deciding whether a TikTok DM API is the right search target, read TikTok DM API: why there is no official endpoint first. If you already know you need live intake, the implementation pattern below complements TikTok Data Portability vs live DMs.

The demo you’ll end up with

The demo is a small Node.js service with one /webhook route. It receives UnifyPort events, validates X-Device-Timestamp and X-Device-Signature, parses the event, writes a compact row to your queue, and acknowledges with 200.

It is intentionally boring. The receiver does not call TikTok’s official API, does not poll exports, and does not depend on TikTok-specific payload handling. It trusts the UnifyPort event layer after signature verification. For the official delivery contract, keep the webhook delivery and signature verification docs open while you build.

Setup: give Copilot the contract, not just a wish

Open GitHub Copilot in your editor or assign the task to Copilot coding agent. GitHub’s own docs describe Copilot as a tool for assisting with code, but it still needs precise context. Paste the relevant UnifyPort reference into your prompt: the delivery headers, the signature formula, the standard event envelope, and the TikTok provider capability note.

Prompt 1:

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.

Copilot should produce something close to this shape:

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();
});

app.listen(3000, () => console.log('webhook receiver listening on :3000'));

Register the webhook endpoint

After the receiver is deployed over HTTPS, create a UnifyPort webhook endpoint. The real reference route is POST /v1/webhook-endpoints; subscribed_events accepts exact public event names or ["*"], and signing_secret enables the signature header.

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"
}'

If you want the broader build-log version of this workflow, compare it with the AI coding agent auto-reply bot tutorial.

Run it and watch a message arrive

When a TikTok message is observed on the connected account, UnifyPort delivers the same envelope documented for standard events. Your handler should rely on field names from the docs, not on guessed provider-specific JSON.

{
  "id": "evt_2f9c1a4b7e",
  "type": "message.received",
  "provider": "tiktok",
  "account_id": "acc_8c21d0",
  "occurred_at": "2026-06-08T12:34:56Z",
  "data": {
    "conversation": { "id": "conv_778899", "type": "user" },
    "sender": { "id": "user_4004", "type": "user", "name": "Jordan Lee" },
    "message": {
      "id": "msg_3003",
      "text": "Hi, is this item still available?",
      "direction": "inbound",
      "sent_at": "2026-06-08T12:34:55Z"
    }
  }
}

Extend it safely

The next prompt should be about operations, not features:

Add idempotency using X-Device-Event-Id. Deduplicate retries, keep the raw event in storage, and route only inbound messages to the triage queue. Do not drop unknown event types; acknowledge them after logging.

That prompt keeps Copilot inside the documented contract. After the queue is reliable, you can add Slack notifications, CRM upserts, or an AI classification worker. Because the event shape is normalized, adding WhatsApp, LINE, Zalo, Telegram, or X later is the same receiver with different account connections.

Limitations and trade-offs

Use TikTok’s official developer APIs when your product needs official content publishing, login, research, or data portability exports. Use UnifyPort when the job is live inbound message intake from an existing account through an unofficial interface. The unofficial path does not give you TikTok’s official product scopes; it gives your backend a signed event stream you can verify and store.

FAQ

Can GitHub Copilot build the whole TikTok inbox for me?

It can scaffold the receiver, tests, and queue code, but you still need to review the signature verification, deployment secrets, and storage behavior.

Is TikTok Data Portability the same as live DMs?

No. TikTok’s official Data Portability docs list direct-message export scopes and data categories. A support inbox usually needs a live event stream, not an export workflow.

Which UnifyPort event should I subscribe to?

Use message.received for inbound message intake. Use ["*"] only when you are building a full event collector and are prepared to store additional event types.

What should the receiver store first?

Store the raw event, id, type, provider, account_id, occurred_at, data.conversation.id, data.sender.id, and data.message.id. Then build routing views from that durable record.

Sources checked on 2026-08-29

UnifyPort API

Turn messaging integration into a stable product pipeline.

Start by sending through one API, then bring every inbound message back into your business system with standard events.