Agent skill
TikTok Webhooks Skill
Receive and verify TikTok for Developers webhooks (Login Kit, Content Posting, Data Portability). Use when setting up TikTok webhook handlers, debugging TikTok-Signature verification, or handling events like authorization.removed, video.upload.failed, video.publish.completed, or portability.download.ready. Not for TikTok Shop — see tiktok-shop-webhooks.
Install this skill
npx skills add hookdeck/webhook-skills --skill tiktok-webhooks
Webhooks from the TikTok for Developers platform (developers.tiktok.com) — Login Kit, Content Posting / Video Kit, and Data Portability. These are not TikTok Shop webhooks, which use a different portal and signature scheme — see tiktok-shop-webhooks.
When to Use This Skill
- How do I receive TikTok webhooks?
- How do I verify the TikTok-Signature header?
- Why is my TikTok webhook signature verification failing?
- How do I handle authorization.removed, video.upload.failed, video.publish.completed, or portability.download.ready events?
- How do I reject replayed TikTok webhook deliveries?
Verification (core)
TikTok has no webhook SDK — verify manually. The TikTok-Signature header looks like t=1633174587,s=<hex>. The signature is HMAC-SHA256(client_secret, "<t>.<raw_body>"), hex-encoded. Verify against the raw request body (don't JSON.parse first) and reject stale timestamps to block replay.
const crypto = require('crypto');
function verifyTikTokWebhook(rawBody, header, clientSecret, toleranceSec = 300) {
if (!header || !clientSecret) return false;
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('='))
);
const { t, s } = parts;
if (!t || !s) return false;
// Reject stale timestamps (no tolerance window is documented; 5 min is sane).
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(t));
if (!Number.isFinite(age) || age > toleranceSec) return false;
const raw = Buffer.isBuffer(rawBody) ? rawBody.toString('utf8') : rawBody;
const expected = crypto
.createHmac('sha256', clientSecret)
.update(`${t}.${raw}`, 'utf8')
.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(s, 'hex'), Buffer.from(expected, 'hex'));
} catch {
return false; // length mismatch = invalid
}
}
For complete handlers with route wiring, event dispatch, and tests, see:
Common Event Types
TikTok for Developers currently defines four webhook events:
| Event | Triggered When |
|---|---|
authorization.removed | A user deauthorizes your app (access token already revoked) |
video.upload.failed | A video uploaded via Video Kit fails to upload |
video.publish.completed | A video uploaded via Video Kit is published by the user |
portability.download.ready | Data requested via the Data Portability API is ready to download |
Watch the exact names: it is
video.publish.completed(not.complete), and there is novideo.publish.failed. See references/overview.md for payload details.
Payload Structure
Events are POSTed as JSON:
{
"client_key": "awx4...",
"event": "video.publish.completed",
"create_time": 1633174587,
"user_openid": "0f9c...",
"content": "{\"share_id\":\"video.7107...\"}"
}
content is a serialized JSON string — parse it separately after parsing the envelope. portability.download.ready has no user_openid.
Environment Variables
TIKTOK_CLIENT_SECRET=your_app_client_secret # signs the webhook; from the developer portal
The webhook signing key is your app's client secret — the same secret used for OAuth. There is no separate webhook secret.
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 tiktok --path /webhooks/tiktok
Register the printed HTTPS URL as your callback URL in the TikTok developer portal and subscribe to events.
Reference Materials
- references/overview.md - TikTok webhook concepts, events, payloads
- references/setup.md - Developer portal configuration
- references/verification.md - Signature verification details and gotchas