asynchronous webhook processing
AWS Lambda webhook processing
BigCommerce API webhooks
BigCommerce developer integration
BigCommerce order webhook
BigCommerce webhook payload optimization
BigCommerce webhook payloads
BigCommerce webhook scaling
distributed systems ecommerce
ecommerce event processing
edge computing webhooks
elasticity flash sale architecture
event driven architecture flash sales
event streaming TikTok Shop
flash sale queue management
flash sale traffic management
handling webhook bursts
high concurrency order processing
high throughput webhook receiver
high velocity webhook management
Kafka for flash sales
live shopping order spikes
managing high volume webhooks
microservices flash sale
RabbitMQ webhook queue
real time inventory sync TikTok
Redis webhook queue
resilient webhook architecture
scalable event bus ecommerce
scaling TikTok Shop API
scaling webhooks ecommerce
serverless event ingestion
serverless webhook ingestion
social commerce architecture
social commerce checkout spikes
social commerce flash sale webhooks
TikTok commerce backend scaling
TikTok live commerce integration
TikTok Shop API integration
TikTok Shop inventory management
TikTok Shop live stream webhooks
TikTok Shop order sync
TikTok Shop seller API
TikTok Shop webhook integration
webhook backpressure management
webhook circuit breaker
webhook concurrency handling
webhook delivery reliability
webhook idempotency ecommerce
webhook overload prevention
webhook payload ingestion
webhook queueing AWS SQS
webhook rate limiting
webhook retry strategy
Tik Tok Shop Big Commerce Flash Sales Managing High Velocity Social Commerce Webhooks
TikTok Shop & BigCommerce Flash Sales: Managing High-Velocity Social Commerce Webhooks
Traditional e-commerce webhook traffic follows a predictable diurnal curve. Traffic builds through the morning, peaks in the early evening, and fades overnight — the kind of load a standard auto-scaling group absorbs without drama.
Social commerce live-streams break that model completely.
When a creator on TikTok Shop flashes a limited-stock item to a live audience and says "link in bio, go," checkout demand stops being a curve and becomes a vertical line. In the space of a second or two, a backend that normally sees a few requests a minute can be hit with thousands of concurrent order, payment, and inventory webhooks.
Industry trackers disagree on the exact share of TikTok Shop's revenue that comes from live shopping — estimates for 2026 range from roughly 10% up toward the mid-20s percent of platform GMV depending on the source and methodology, and the figure is meaningfully higher in Southeast Asia than in the US. What every tracker agrees on is the shape of the traffic: individual livestream sessions are reported to swing from a few hundred dollars in sales to tens of thousands of dollars within a single hour, and that revenue lands as a burst, not a trickle. If your integration bridges TikTok Shop and BigCommerce, that burst is an architectural problem, not just a marketing one. Handled poorly, it produces dropped orders, oversold inventory, API rate-limit lockouts, and — in BigCommerce's case — webhooks that get automatically disabled mid-sale.
This piece lays out an architecture for surviving that burst: how to decouple ingestion from processing, how to verify and de-duplicate events correctly for both platforms, how to batch inventory writes against BigCommerce's real rate limits, and how to recover cleanly when something still breaks.
A note on scope: TikTok Shop's Partner API and BigCommerce's REST API both change fairly often. The specifics below (header names, rate-limit numbers, retry windows) were checked against current platform documentation as of September 2026 and are sourced at the end of this article — but always verify against the live docs before shipping, especially anything involving signature verification.
- Why Social Commerce Traffic Breaks Synchronous Architectures Metric Traditional BigCommerce Storefront TikTok Shop Live Flash Sale Traffic onset Gradual ramp over 15–45 minutes Near-instant burst, effectively 0 to peak in under a second Concurrency Spread across browsing, carts, checkout Concentrated on one-tap checkouts during a narrow window Event velocity Tens of webhooks per minute at peak Can spike into the thousands per second during a drop Inventory risk Standard per-order sync Multi-channel race condition between TikTok Shop and BigCommerce When a burst like this hits a naive, synchronous integration, the failure sequence is predictable:
The synchronous trap. The receiver accepts a TikTok Shop webhook, looks up the order, calls the BigCommerce Catalog or Inventory API to adjust stock, and only then returns a response to TikTok. Every one of those steps holds the HTTP connection open.
Thread starvation. As concurrent webhooks pile up, response times climb from tens of milliseconds to multiple seconds. TikTok Shop's delivery layer treats a slow or missing response as a failed delivery.
Retry amplification. TikTok Shop retries failed deliveries, adding to the load your already-struggling receiver is trying to handle — compounding the spike instead of relieving it.
BigCommerce throttling. Meanwhile, your workers are hammering BigCommerce with one inventory update per order. BigCommerce's default API plan allows 150 requests per 30 seconds per store, per API client — a limit that a few hundred concurrent orders blows through in seconds, returning 429 Too Many Requests.
Webhook disablement. If your own downstream BigCommerce webhook consumer (for events like store/order/statusUpdated) can't keep up and starts timing out, BigCommerce logs delivery exceptions and, after roughly 48 hours of continued failure across 11 retry attempts, disables the webhook by flipping its is_active flag to false — severing sync until someone notices and re-enables it.
The fix, as with most high-throughput webhook problems, is to stop doing any of that work inside the HTTP request/response cycle at all.
- Architecture: Buffer-First Ingestion The core rule: your public webhook endpoint does no business logic. It verifies the signature, drops the raw payload on a queue, and returns a fast acknowledgment — nothing else.
Code example
Copy code
HIGH-VELOCITY INGESTION PIPELINE
┌────────────────┐ ┌─────────────────────────┐ ┌──────────────────┐
│ TikTok Shop │─────►│ Light Receiver Edge API │─────►│ Ingestion Bus │
│ Webhook Engine │ │ (Fastify / API Gateway) │ │ (AWS SQS / Kafka)│
└────────────────┘ └─────────────────────────┘ └────────┬─────────┘
│
┌─────────┴─────────┐
│ Async Worker Pool │
└─────────┬─────────┘
│
┌─────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Idempotent │ │ Inventory Delta │ │ BigCommerce │
│ Redis Lock │ │ Aggregator │ │ Inventory API │
└───────────┘ └──────────────────┘ └──────────────────┘
Ingress layer. A lightweight Fastify/Node service, Go service, or serverless function (API Gateway + Lambda, Cloudflare Workers) whose only jobs are HMAC verification and enqueueing. Budget under 20ms of execution time.
Message bus. Amazon SQS Standard queues are a good default here: AWS documents standard queues as supporting a very high, effectively unlimited number of API calls per second per action, with at-least-once delivery and best-effort ordering. That "best-effort ordering, at-least-once delivery" tradeoff is exactly why the idempotency layer in Section 4 isn't optional — it's what makes an unordered, duplicate-tolerant queue safe to use.
If you need strict per-order sequencing instead of timestamp-based reconciliation, SQS FIFO queues are the alternative, and AWS has been steadily raising FIFO throughput ceilings — high-throughput mode now supports up to 70,000 transactions per second per API action in several regions (lower in some others), which is generally enough headroom even for a very large flash sale. Standard queues remain the simpler default; reach for FIFO only if strict ordering genuinely matters more than raw throughput and code simplicity.
Worker pool. Node.js/TypeScript, Go, or Python consumers that pull from the queue, enforce idempotency, aggregate inventory deltas, and make rate-limit-aware calls to BigCommerce.
- Verifying Webhook Authenticity — Correctly This is the part most reference architectures get wrong, because TikTok Shop's signature scheme is easy to confuse with the general TikTok for Developers webhook scheme, which is a different product surface using a different format. Mixing them up means your signature check silently never passes (or worse, silently never fails).
TikTok Shop's actual scheme
For TikTok Shop specifically (Partner Center / Shop Open API webhooks — order, package, product, and message events), the signature travels in the Authorization header, with no Bearer prefix. It is a lowercase-hex HMAC-SHA256, computed as:
Code example
Copy code
signature = HMAC_SHA256(key = app_secret, message = app_key + raw_request_body)
A few things to get right:
Sign the exact raw bytes of the body — parsing the JSON and re-serializing it before signing will not match.
There is no timestamp baked into the signature, so this scheme offers no built-in replay protection. Don't rely on the signature alone to dedupe; use the event's tts_notification_id for that (covered in Section 4).
Return 401 Unauthorized for a bad signature, not 400.
This is unrelated to TikTok Shop's separate API request signing scheme (used when you call TikTok Shop's API, which signs the path, sorted query params, and body with a sign parameter). Don't reuse that logic for webhook verification, and vice versa.
Code example
Copy code
import crypto from 'crypto';
/**
- Verifies a TikTok Shop webhook (Partner Center / Shop Open API).
- Signature arrives in the
Authorizationheader — no Bearer prefix. */ export function verifyTikTokShopSignature( rawBody: Buffer, authorizationHeader: string | undefined, appKey: string, appSecret: string ): boolean { if (!authorizationHeader) return false;
const message = Buffer.concat([Buffer.from(appKey, 'utf8'), rawBody]);
const expected = crypto
.createHmac('sha256', appSecret)
.update(message)
.digest('hex');
const expectedBuf = Buffer.from(expected, 'utf8');
const givenBuf = Buffer.from(authorizationHeader, 'utf8');
if (expectedBuf.length !== givenBuf.length) return false;
return crypto.timingSafeEqual(expectedBuf, givenBuf);
}
Common TikTok Shop webhook events worth planning your handler switch statement around: ORDER_STATUS_CHANGE, PACKAGE_UPDATE, RECIPIENT_ADDRESS_UPDATE, PRODUCT_STATUS_CHANGE, SELLER_DEAUTHORIZATION, and NEW_MESSAGE (customer-service events, internally event type 14). TikTok Shop's own guidance asks receivers to acknowledge quickly — documentation for the adjacent Customer Service webhooks specifies responding within 3 seconds, which is a reasonable target to hold your whole ingress layer to.
Verifying BigCommerce's webhooks too
If your pipeline is bidirectional — reacting to BigCommerce events like store/order/statusUpdated to push fulfillment or cancellation state back to TikTok Shop — verify those inbound webhooks as well. BigCommerce now documents webhook signing against the open Standard Webhooks specification: deliveries carry webhook-id, webhook-timestamp, and webhook-signature (formatted v1,) headers. The signing key is your app's client secret, base64-encoded before use.
Code example
Copy code
import crypto from 'crypto';
export function verifyBigCommerceSignature(
rawBody: Buffer,
webhookId: string,
webhookTimestamp: string,
webhookSignatureHeader: string, // e.g. "v1,BASE64SIG v1,BASE64SIG2"
clientSecret: string,
toleranceSeconds = 300
): boolean {
// Reject stale deliveries — Standard Webhooks recommends a 5-minute tolerance.
const age = Math.abs(Date.now() / 1000 - parseInt(webhookTimestamp, 10));
if (age > toleranceSeconds) return false;
const signedContent = ${webhookId}.${webhookTimestamp}.${rawBody.toString('utf8')};
const key = Buffer.from(clientSecret, 'utf8').toString('base64');
const expected = crypto
.createHmac('sha256', Buffer.from(key, 'base64'))
.update(signedContent)
.digest('base64');
// webhook-signature can carry multiple space-separated "v1," values
// during secret rotation — accept the request if any of them match.
return webhookSignatureHeader
.split(' ')
.some((entry) => {
const [, sig] = entry.split(',');
if (!sig) return false;
const a = Buffer.from(sig, 'base64');
const b = Buffer.from(expected, 'base64');
return a.length === b.length && crypto.timingSafeEqual(a, b);
});
}
Worth flagging: BigCommerce's own docs, as of this writing, describe the scheme by pointing developers to Standard Webhooks client libraries rather than naming the headers outright, and don't clearly state whether signing is fully GA across every webhook type. Log the incoming headers on your first real delivery to confirm what you're actually receiving before trusting this in production.
- Idempotency & Out-of-Order Events At-least-once delivery is the norm on both platforms, and under concurrent retries and parallel workers, events will arrive duplicated or out of order — a cancellation notification landing before the order-created notification, a paid event processed twice.
For TikTok Shop, key your idempotency lock on tts_notification_id, since — as noted above — the webhook signature carries no timestamp and gives you no replay signal on its own. For BigCommerce, the hash field on each delivery plus the resource id/scope pair works well as a dedup key.
Code example
Copy code
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
export async function processWebhookWithIdempotency(
notificationId: string,
eventType: string,
eventTimestamp: number,
processFn: () => Promise
): Promise {
const idempotencyKey = idempotency:${eventType}:${notificationId};
const timestampKey = entity_last_ts:${notificationId};
// Atomic lock: only the first delivery of this notification proceeds.
const isNewEvent = await redis.set(idempotencyKey, 'LOCKED', 'EX', 86400, 'NX');
if (!isNewEvent) {
return true; // Duplicate — acknowledge so the queue message clears.
}
// Out-of-order guard: skip if a newer update for this entity already landed.
const lastProcessedTime = await redis.get(timestampKey);
if (lastProcessedTime && parseInt(lastProcessedTime, 10) > eventTimestamp) {
return true;
}
await processFn();
await redis.set(timestampKey, eventTimestamp.toString(), 'EX', 604800);
return true;
}
- Rate Limits & Batching — Against the Real Numbers Both platforms will throttle you the moment a flash sale sends a burst of individual writes, but they throttle differently, and the fix on the BigCommerce side is more specific than "add a retry wrapper."
BigCommerce: use the Inventory API, not per-variant Catalog calls
BigCommerce's default API rate plan allows 150 requests per 30 seconds, per store, per API client — response headers X-Rate-Limit-Requests-Quota, X-Rate-Limit-Requests-Left, X-Rate-Limit-Time-Window-Ms, and X-Rate-Limit-Time-Reset-Ms tell you exactly where you stand on every call. Blow through it and you get 429 Too Many Requests; sustained abuse can also trigger a 509 Bandwidth Limit Exceeded. (Enterprise stores can be on an "Unlimited" plan, but that's still bounded by underlying infrastructure limits, so batching remains worthwhile regardless.)
The original approach of calling the Catalog API's variant-batch endpoint (PUT /v3/catalog/variants) doesn't scale well for this use case on its own — that endpoint currently caps out at around 50 variants per batch call, which is a small ceiling during a burst of thousands of orders. The better fit for order-driven, delta-style inventory changes is BigCommerce's dedicated Inventory API, specifically the relative-adjustment endpoint:
Code example
Copy code
POST https://api.bigcommerce.com/stores/{store_hash}/v3/inventory/adjustments/relative
BigCommerce's own guidance is explicit that relative adjustments are the right tool "when you do not know absolute quantities" — their example is precisely order-driven changes coming from a third party, which is exactly this scenario. That endpoint accepts up to roughly 2,000 items per payload, a far larger batch ceiling than the Catalog API offers, and it's location-aware if you run multi-warehouse fulfillment.
Code example
Copy code
/**
- Record an inventory reduction in Redis during a high-velocity flash sale. */ export async function queueInventoryDecrement(sku: string, quantity: number) { await redis.hincrby('inventory_delta:bigcommerce', sku, -Math.abs(quantity)); }
/**
- Runs every ~2s. Flushes aggregated deltas to BigCommerce's Inventory API
- in a single relative-adjustment batch call instead of one call per order. */ export async function flushInventoryDeltasToBigCommerce() { const cacheKey = 'inventory_delta:bigcommerce';
const pipeline = redis.pipeline();
pipeline.hgetall(cacheKey);
pipeline.del(cacheKey);
const [[, pendingDeltas]] = (await pipeline.exec()) as [[Error | null, Record]];
if (!pendingDeltas || Object.keys(pendingDeltas).length === 0) return;
const items = Object.entries(pendingDeltas).map(([sku, deltaStr]) => ({
sku,
location_id: DEFAULT_LOCATION_ID,
quantity: parseInt(deltaStr, 10),
}));
try {
await bigCommerceClient.post('/v3/inventory/adjustments/relative', {
reason: 'TikTok Shop live-sale sync',
items,
});
} catch (error) {
console.error('Inventory batch flush failed, restoring deltas', error);
for (const [sku, deltaStr] of Object.entries(pendingDeltas)) {
await redis.hincrby(cacheKey, sku, parseInt(deltaStr, 10));
}
}
}
Also worth budgeting for: BigCommerce webhook payloads are intentionally thin — typically just a resource type and id, not the full object. If your pipeline reacts to BigCommerce's own webhooks (rather than only pushing outbound), every event requires a follow-up API call to fetch the actual resource, which roughly doubles the API calls that event stream costs you. Factor that into your 150-requests-per-30-seconds budget.
TikTok Shop: adapt to 429s, don't hard-code a ceiling
Unlike BigCommerce's published numeric quota, TikTok Shop's Partner API uses dynamic QPS allocation — your effective throughput is computed based on the number of shops your app is authorized for and the specific endpoint, and TikTok doesn't expose an API to query your current quota. In practice this means: don't hard-code a requests-per-second assumption for order or fulfillment calls. Build your client to react to 429 responses and back off, rather than trying to stay under a number you can't actually look up.
Exponential backoff with full jitter, for both platforms
Code example
Copy code
export async function executeWithRetry(
fn: () => Promise,
maxRetries = 5,
baseDelayMs = 1000,
maxDelayMs = 30000
): Promise {
let attempt = 0;
while (attempt < maxRetries) {
try {
return await fn();
} catch (error: any) {
attempt++;
const status = error.response?.status;
const isRateLimited = status === 429 || status === 509;
if (!isRateLimited && attempt >= maxRetries) throw error;
const retryAfterHeader = error.response?.headers?.['retry-after'];
let delayMs: number;
if (retryAfterHeader) {
delayMs = parseInt(retryAfterHeader, 10) * 1000;
} else {
const exponentialDelay = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
delayMs = Math.floor(Math.random() * exponentialDelay); // full jitter
}
console.warn(`[RETRY ${attempt}] Rate limited. Waiting ${delayMs}ms...`);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
throw new Error(Max retries (${maxRetries}) exhausted.);
}
Randomized jitter matters here specifically because a burst of workers all backing off on the same fixed schedule will just re-synchronize their retries into a second spike.
- Full Production Blueprint: Ingress & Worker
- Ingress edge receiver
Code example
Copy code
import Fastify from 'fastify';
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
import { verifyTikTokShopSignature } from './cryptoUtils';
const fastify = Fastify({ logger: true });
const sqs = new SQSClient({ region: process.env.AWS_REGION });
const SQS_QUEUE_URL = process.env.TIKTOK_WEBHOOK_QUEUE_URL!;
const TIKTOK_APP_KEY = process.env.TIKTOK_APP_KEY!;
const TIKTOK_APP_SECRET = process.env.TIKTOK_APP_SECRET!;
// Preserve raw bytes — required for signature verification.
fastify.addContentTypeParser(
'application/json',
{ parseAs: 'buffer' },
(req, body, done) => done(null, body)
);
fastify.post('/webhooks/tiktok-shop', async (request, reply) => {
const rawBody = request.body as Buffer;
const authHeader = request.headers['authorization'] as string | undefined;
if (!verifyTikTokShopSignature(rawBody, authHeader, TIKTOK_APP_KEY, TIKTOK_APP_SECRET)) {
return reply.status(401).send({ error: 'Invalid signature' });
}
try {
const parsed = JSON.parse(rawBody.toString('utf8'));
const eventType = parsed.type ?? 'UNKNOWN';
const notificationId = parsed.tts_notification_id;
await sqs.send(
new SendMessageCommand({
QueueUrl: SQS_QUEUE_URL,
MessageBody: rawBody.toString('utf8'),
MessageAttributes: {
EventType: { DataType: 'String', StringValue: eventType },
NotificationId: { DataType: 'String', StringValue: notificationId ?? '' },
},
})
);
return reply.status(200).send({ code: 0, message: 'ACCEPTED' });
} catch (error) {
fastify.log.error(error, 'Error enqueueing webhook payload');
return reply.status(500).send({ error: 'Internal Queue Error' });
}
});
fastify.listen({ port: 3000, host: '0.0.0.0' }, (err) => {
if (err) throw err;
console.log('Webhook ingress active on port 3000');
});
- Async worker
Code example
Copy code
import { SQSClient, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
import { processWebhookWithIdempotency } from './idempotency';
import { executeWithRetry } from './retryUtils';
import { queueInventoryDecrement } from './inventoryAggregator';
const sqs = new SQSClient({ region: process.env.AWS_REGION });
const SQS_QUEUE_URL = process.env.TIKTOK_WEBHOOK_QUEUE_URL!;
async function startWorker() {
console.log('SQS consumer worker started...');
while (true) {
const response = await sqs.send(
new ReceiveMessageCommand({
QueueUrl: SQS_QUEUE_URL,
MaxNumberOfMessages: 10,
WaitTimeSeconds: 20, // long polling
})
);
if (!response.Messages?.length) continue;
for (const message of response.Messages) {
if (!message.Body || !message.ReceiptHandle) continue;
const payload = JSON.parse(message.Body);
const notificationId = payload.tts_notification_id;
const eventType = payload.type;
const timestamp = payload.timestamp ?? Math.floor(Date.now() / 1000);
const handled = await processWebhookWithIdempotency(
notificationId,
eventType,
timestamp,
() => handleWebhookBusinessLogic(eventType, payload)
);
if (handled) {
await sqs.send(
new DeleteMessageCommand({ QueueUrl: SQS_QUEUE_URL, ReceiptHandle: message.ReceiptHandle })
);
}
}
}
}
async function handleWebhookBusinessLogic(eventType: string, payload: any) {
switch (eventType) {
case 'ORDER_STATUS_CHANGE':
if (payload.data?.order_status === 'AWAITING_SHIPMENT') {
for (const item of payload.data.item_list) {
await queueInventoryDecrement(item.seller_sku, item.quantity);
}
await executeWithRetry(async () => {
// Create/update the corresponding BigCommerce order.
});
}
break;
default:
console.log(Unhandled event type: ${eventType});
}
}
startWorker();
- Monitoring, Dead Letter Queues, and Recovery Configure dashboards (CloudWatch, Datadog, Grafana — whatever you already run) around three metrics:
Ingress latency (p99) — should stay comfortably under 50ms. Anything creeping toward 200ms means your edge receiver is blocking on I/O somewhere it shouldn't be.
Queue backlog depth — expected to climb during a live drop, but should drain back to zero within a few minutes after the sale ends.
Worker error rate (429/5xx) — your early signal of downstream pressure on BigCommerce or database contention.
Dead letter queues. Set SQS maxReceiveCount to a small number (5 is a common default) so a malformed "poison pill" payload can't loop indefinitely against your worker. Alert immediately when the DLQ depth rises above zero, and build a one-click replay path for once the underlying bug or outage is fixed.
BigCommerce-specific recovery signals. Subscribe to store/hook/deliveryException so you get told directly when your own webhook deliveries are struggling, rather than discovering it after the fact. BigCommerce's delivery-exception codes are worth knowing by number:
Code Meaning
90001 Delivery failed, will retry (BigCommerce rate-limits this notice to once per 10 minutes)
90002 All retries exhausted — the webhook has been disabled
90003 Your destination domain has been blocklisted
Two behaviors here catch people off guard. First, retries and disablement are tracked per destination domain, not per individual webhook — if yourapp.com/webhook-orders and yourapp.com/webhook-inventory both point at the same domain, failures on one affect retry behavior for both. Second, if a domain's delivery success ratio drops below 90% within a rolling 2-minute window, BigCommerce blocklists that domain for 3 minutes — a short, automatic circuit-breaker that a struggling ingress layer can trigger on itself during exactly the kind of spike this article is about. Keeping ingress response time low isn't just about your own throughput; it's what keeps BigCommerce from cutting you off.
Code example
Copy code
DEAD LETTER QUEUE (DLQ) RECOVERY
┌──────────────────┐ Fail 5x ┌──────────────────┐ ┌──────────────────┐
│ Primary Ingest │──────────────►│ Dead Letter Queue│───(Inspect)──►│ Fix Code/Outage │
│ Queue (SQS) │ │ (DLQ Buffer) │ └────────┬─────────┘
└──────────────────┘ └──────────────────┘ │
▲ │
└───────────────────────── (Replay Payload) ─────────────────────────┘
- Deployment Readiness Checklist Code example Copy code [ ] Ingestion Decoupling └─ Public webhook endpoint performs ONLY signature validation & queue publish. └─ Measured p99 endpoint response time is < 20ms under load.
[ ] Signature Verification (both directions, if bidirectional)
└─ TikTok Shop: Authorization header, HMAC-SHA256(app_secret, app_key + raw_body), 401 on failure.
└─ BigCommerce: Standard Webhooks headers (webhook-id/-timestamp/-signature), 5-min timestamp tolerance.
[ ] Idempotency Safeguards
└─ Redis atomic lock (SET NX) keyed on tts_notification_id / BigCommerce hash+id, 24h+ TTL.
└─ Out-of-order resolution enforced via per-entity timestamp tracking.
[ ] Rate-Limiting & Batching
└─ BigCommerce inventory writes routed through Inventory API relative-adjustment batches
(up to ~2,000 items/request), not per-order Catalog calls.
└─ TikTok Shop calls back off on 429 rather than assuming a fixed QPS ceiling.
└─ Exponential backoff with full jitter on all outbound retries.
[ ] Resilience & Dead Letter Queues
└─ SQS DLQ active with a small maxReceiveCount (e.g. 5).
└─ Replay path tested in staging.
[ ] Platform Safety
└─ store/hook/deliveryException subscribed and alerting on 90001/90002/90003.
└─ Ingress latency kept well clear of BigCommerce's 90%-success/2-minute blocklist trigger.
Conclusion
Surviving a TikTok Shop flash sale on a BigCommerce backend comes down to refusing to do real work inside the webhook request/response cycle: verify the signature correctly (and per the right scheme — TikTok Shop's own, not the generic TikTok for Developers one), buffer everything through a queue built for bursty at-least-once delivery, de-duplicate on the right ID, and push inventory changes through BigCommerce's Inventory API in batches instead of one call per order. None of the individual pieces are exotic — the difference between an integration that survives a live-stream spike and one that doesn't is almost always which specific endpoint, header, and batch size you reached for.
Sources checked for this piece
BigCommerce Developer Center — API rate limits, Webhooks overview, Inventory adjustments, Product Variants / batch update
Hookdeck — Guide to BigCommerce Webhooks, TikTok Shop Webhooks skill and its signature verification reference
TikTok for Developers — Rate limits, Shop Management APIs
AWS — SQS queue types, High throughput for FIFO queues
Standard Webhooks specification — standardwebhooks.com
Industry GMV/live-shopping estimates: multiple third-party trackers (Dashboardly, Axis Intelligence, Momentum Works) — treated as directional estimates rather than official platform figures, given the range of numbers reported across sources.
Rate limits, header names, and retry windows are the kind of detail platforms change without much notice — re-verify against current docs before a launch you can't afford to get wrong.
Top comments (0)