Guide to TikTok Shop Webhooks: Features and Best Practices
TikTok Shop webhooks notify your systems when commerce activity happens on a connected shop: an order changes status, a package updates, a product's status changes. For any app in the TikTok Shop ecosystem, whether an order management tool, a fulfillment integration, or a catalog sync, webhooks are how you react without polling the Partner API.
This guide covers how TikTok Shop webhooks work, the events you can subscribe to, how to verify the signature (which arrives in the Authorization header, not a signature-specific one), the delivery behavior, and the best practices that keep a shop integration reliable in production.
What are TikTok Shop webhooks?
TikTok Shop webhooks are HTTP POSTs delivered to a URL you configure per app, each carrying a JSON payload about a shop event. The webhook signature scheme is distinct from TikTok Shop's API request signing, and the signature is delivered in the standard Authorization header rather than a custom signature header.
Each payload carries a numeric type, a tts_notification_id, a shop_id, a timestamp, and a data object. The numeric type is unreliable to branch on; use the subscribed event type instead.
TikTok Shop webhook features
| Feature | Details |
|---|---|
| Configuration | Partner Center (per app) or the Events API |
| Signature location | Authorization header (no Bearer prefix) |
| Signature scheme | lowercase-hex HMAC-SHA256 over app_key + raw body, keyed with app_secret |
| Endpoint requirements | HTTPS on a domain (no IP, no custom port), TLS 1.2+ |
| Acknowledgement | HTTP 200 with an empty body within 3 seconds |
| Rejected signature | Endpoint returns 401 |
| Delivery | At-least-once, 4 retries after failure (2 min, 30 min, 3 h, 12 h) |
| Deduplication | tts_notification_id |
| SDKs | App-specific downloads from Partner Center (not on npm/PyPI) |
Common events
TikTok Shop groups events by resource. Order, package, and product events are the ones most integrations start with:
| Event | Fires when |
|---|---|
ORDER_STATUS_CHANGE | An order moves to a new status |
PACKAGE_UPDATE | A package (fulfillment unit) is updated |
PRODUCT_STATUS_CHANGE | A product's status changes |
Subscribe only to the events you process, and branch on the subscribed event_type rather than the numeric type field in the payload. Availability of specific events can vary by app scope and market, so check your app's configuration in Partner Center for the events you're entitled to.
Setting up TikTok Shop webhooks
Configure webhooks per shop in Partner Center (App & Service > your app > Basic Information > Developing > Webhook URL / Event subscriptions), or manage them via the Events API:
curl -X PUT "https://open-api.tiktokglobalshop.com/event/202309/webhooks" \
-H "Content-Type: application/json" \
-d '{ "event_type": "ORDER_STATUS_CHANGE", "address": "https://your-app.example.com/webhook" }'
Your endpoint must be HTTPS on a domain (no raw IP, no custom port) with TLS 1.2 or higher, and must return HTTP 200 with an empty body within 3 seconds. A 401 signals that you rejected the signature.
Securing TikTok Shop webhooks
The signature arrives in the Authorization header (with no Bearer prefix) as a lowercase-hex HMAC-SHA256. The signing base is your app_key concatenated with the raw request body, and the key is your app_secret. Compute it over the exact raw body and compare in constant time.
const crypto = require("crypto");
const APP_KEY = process.env.TTS_APP_KEY;
const APP_SECRET = process.env.TTS_APP_SECRET;
function verify(rawBody, authHeader) {
const base = APP_KEY + rawBody; // app_key + raw body
const expected = crypto.createHmac("sha256", APP_SECRET).update(base).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(authHeader || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const auth = req.headers["authorization"];
if (!verify(req.body.toString("utf8"), auth)) {
return res.sendStatus(401);
}
res.status(200).send(""); // empty 200 within 3 seconds
processQueue.add(JSON.parse(req.body)); // process asynchronously
});
The same check in Python:
import hashlib
import hmac
import os
APP_KEY = os.environ["TTS_APP_KEY"]
APP_SECRET = os.environ["TTS_APP_SECRET"].encode()
def verify(raw_body: str, auth_header: str) -> bool:
base = (APP_KEY + raw_body).encode()
expected = hmac.new(APP_SECRET, base, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, auth_header or "")
TikTok Shop webhook limitations and pain points
The signature is in the Authorization header, over app_key + body
The Problem: Two things trip people up: the signature lives in Authorization (which developers often reserve for bearer tokens), and the signing base is app_key + raw body, not the body alone. Miss either and every delivery fails verification.
Why It Happens: TikTok Shop's webhook signing differs from its API request signing and reuses the Authorization header for the digest.
Workarounds:
- Read the digest from
Authorization(noBearerprefix) and signapp_key + raw bodywithapp_secret. - Verify against the exact raw body and compare in constant time.
How Hookdeck Can Help: Hookdeck verifies the TikTok Shop signature at the edge, so your app receives pre-verified events and doesn't have to special-case the header or the concatenated signing base.
The three-second empty-200 window
The Problem: You must return an empty-bodied 200 within 3 seconds. Any synchronous processing eats the budget, and a slow response counts as a failure that triggers retries.
Why It Happens: TikTok Shop caps how long it waits so one slow endpoint doesn't hold up delivery.
Workarounds:
- Verify, enqueue, and return an empty 200 immediately; process off a queue.
- Do no blocking work (API calls, DB writes) before responding.
How Hookdeck Can Help: Hookdeck acknowledges TikTok Shop within milliseconds and durably queues events, decoupling the 3-second window from your processing time.
At-least-once delivery with a fixed retry ladder
The Problem: Delivery is at-least-once with 4 retries after a failure (at 2 minutes, 30 minutes, 3 hours, and 12 hours), then TikTok Shop gives up. The same event arrives more than once, and after the ladder is exhausted it's gone.
Why It Happens: Retries prioritize eventual delivery, and the ladder is capped to avoid retrying forever.
Workarounds:
- Dedupe on
tts_notification_id. - Reconcile against the Partner API for anything you can't afford to miss once the ladder is exhausted.
- Make side effects idempotent.
How Hookdeck Can Help: Hookdeck deduplicates on delivery and durably queues events with retry schedules you control, so duplicates don't double-process and a 12-hour ladder isn't your only safety net. See our guide to webhook idempotency.
The numeric type field is unreliable
The Problem: Payloads carry a numeric type, but branching on it is fragile. Handlers keyed to the number can misclassify events.
Why It Happens: The numeric type isn't a stable public contract; the subscribed event type is.
Workarounds:
- Branch on the subscribed
event_type, not the numerictype. - Validate the payload shape per event type before acting.
How Hookdeck Can Help: Hookdeck's filters and transformations let you route and normalize on the fields you trust, so downstream handlers aren't reasoning about the numeric type.
Best practices
Verify the Authorization-header signature correctly
Read the digest from Authorization (no Bearer), sign app_key + raw body with app_secret, lowercase-hex, and compare in constant time against the raw body.
Return an empty 200 within three seconds
Acknowledge immediately with an empty body and defer processing to a queue. See why to process webhooks asynchronously.
Dedupe on tts_notification_id
Use tts_notification_id as your idempotency key so at-least-once delivery doesn't double-process.
Branch on the subscribed event type
Ignore the numeric type and route on the event_type you subscribed to.
Reconcile with the Partner API
The retry ladder gives up after 12 hours. Reconcile periodically so a permanently failed delivery doesn't leave you out of sync.
Make TikTok Shop webhooks production-ready
Hookdeck verifies the signature, deduplicates on tts_notification_id, and durably queues every shop event
Conclusion
TikTok Shop webhooks put order, package, and product activity on a push channel your app can act on, but the details are specific: a signature in the Authorization header computed over app_key + raw body, a hard 3-second empty-200 window, at-least-once delivery on a fixed 4-step retry ladder, and a numeric type field you shouldn't trust. Get any of those wrong and you either reject valid events or double-process duplicates.
That leaves verification, fast acknowledgement, deduplication, and reconciliation on you. Hookdeck verifies the signature, deduplicates on tts_notification_id, and durably queues every event at the edge, so your integration processes verified, unique shop events and never races the 3-second window.
Get started with Hookdeck for free and handle TikTok Shop webhooks reliably in minutes.