Webhooks
Pass webhookUrl on any video-jobs submit call and Megomi POSTs the terminal result there as soon as the job resolves — the same result shape that call would have returned inline. This is the recommended way to collect a result after a 202; see Getting Your Result for the alternative (resubmitting).
Delivery
Section titled “Delivery”- Up to 3 attempts, with a short linear backoff between them.
- A delivery that exhausts its attempts is not retried further — resubmit the original request to get the result inline instead (see Getting Your Result), at no extra charge.
- Every delivery is
Content-Type: application/json,POST, with the same body shape shown below.
Payload
Section titled “Payload”| Field | Type | Description |
|---|---|---|
requestId |
string | The opaque req_… ID from the original submit response |
itemId |
string | Resolved TikTok item ID |
status |
string | success | not-found | blocked | timed-out | cancelled |
requestCount |
number | How many requests coalesced onto this job |
result |
object | null | Same shape as the endpoint you submitted through |
finishReason |
string | null | Present when the job didn’t resolve to success |
Headers & signature
Section titled “Headers & signature”Every delivery carries these headers:
| Header | Value |
|---|---|
X-TTScraper-Request-Id |
The requestId this delivery is for |
X-TTScraper-Timestamp |
Unix seconds when this attempt was sent |
X-TTScraper-Signature |
v1=<hex HMAC-SHA256(webhook secret, timestamp + "." + raw body)> |
To verify a delivery: recompute the HMAC over the raw request body (before any JSON parsing), compare it to the signature in constant time, and reject anything whose timestamp is more than ~5 minutes from your own clock.
The signature covers timestamp + "." + rawBody — not the parsed JSON, so re-serializing the body before verifying will produce a mismatch if your JSON library reorders or reformats keys.
A timestamp check matters as much as the signature itself: without it, a captured delivery (and its valid signature) could be replayed indefinitely.
const crypto = require('node:crypto');
function verify(secret, rawBody, timestamp, signatureHeader) { if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = 'v1=' + crypto .createHmac('sha256', secret) .update(`${timestamp}.${rawBody}`) .digest('hex');
return signatureHeader.length === expected.length && crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));}import hmac, hashlib, time
def verify(secret, raw_body, timestamp, signature_header): if abs(time.time() - float(timestamp)) > 300: return False expected = "v1=" + hmac.new( secret.encode(), f"{timestamp}.{raw_body}".encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature_header, expected){ "requestId": "req_9f1kQ3zP7mLxA2dR", "itemId": "7123456789012345678", "status": "success", "requestCount": 2, "result": { "likes": 128400, "views": 2114500, "comments": 3021, "shares": 884, "saves": 2110, "hasBasket": true }, "finishReason": null}{ "requestId": "req_2wYh8LmN5vRtCx1s", "itemId": "7123456789012345678", "status": "blocked", "requestCount": 1, "result": null, "finishReason": "blocked"}