API reference/Webhook payloads

Webhook payloads

What we send, how to verify it came from us, and what happens when your endpoint is down.

Events

job.completed — the job reached done and its rows are waiting to be read.
job.failed — the job reached failed; the error code is in the payload.
batch.completed — every job in a batch has finished, whatever the outcome.

Payload

{
"event": "job.completed",
"job_id": "8f2c1e40-91a3-4b7e",
"batch_id": "b3f1c0d2-77a4",
"status": "done",
"row_count": 14,
"usage": {
"credits_charged": 1,
"duration_seconds": 782,
"chunk_count": 4
}
}
NOTE
No rows travel in the payload. Fetch the job to collect them, which keeps the read-once rule in one place and means a leaked webhook body exposes nothing extracted.

Signing

Every delivery carries X-TubeExtract-Signature, an HMAC-SHA256 of the raw body using the endpoint’s signing secret, and X-TubeExtract-Timestamp. The signed string is {timestamp}.{body}, so a captured body cannot be replayed later under a new timestamp. Compare with a constant-time function and reject anything older than five minutes.

import crypto from "node:crypto";
 
export function verify(rawBody, headers, secret) {
const timestamp = headers["x-tubeextract-timestamp"];
const signature = headers["x-tubeextract-signature"];
 
// Anything older than five minutes is a replay, not a delivery.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
 
const expected =
"v1=" +
crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
 
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Rotating a secret on the webhooks screen keeps the previous one valid for 24 hours, so a deploy has time to catch up.

Retries

Anything other than a 2xx is retried five times over roughly an hour with growing gaps. Deliveries and their response codes are listed on the job’s detail screen, where a failed one can be replayed by hand without re-running the extraction.

CAREFUL
Deliveries can arrive more than once. Treat the job ID and event name together as the deduplication key.
Was this page useful?Tell us what was missing