API reference/Webhook payloads

Webhook payloads and signature verification

batch.completed, job.completed and job.failed payloads, HMAC-SHA256 signature verification in Node, and the retry schedule.

Events

batch.completed — every video submitted in one POST /v1 has finished, whatever the outcome. This is the event to collect on: the job has settled and its rows are ready to read.
job.completed — one video in the job reached done. Others in the same call may still be running.
job.failed — one video reached failed; its error code is in the payload.
NOTE
Webhooks are included on every paid plan. On Free, registering an endpoint or passing webhook_url is refused with 403 plan_limit, so poll instead.

Payload

Two ids appear, and they are not interchangeable. batch_id is the id POST /v1 answered with, and the one GET /v1/{job_id} accepts. job_id in a job event identifies a single video inside it; reading it as a job returns 404.

batch.completed
{
"event": "batch.completed",
"batch_id": "8f2c1e40-91a3-4b7e",
"counts": {
"done": 2,
"failed": 1,
"total": 3
},
"credits_charged": 280
}
job.completed
{
"event": "job.completed",
"job_id": "c41d09b7-5e2a-4f18",
"batch_id": "8f2c1e40-91a3-4b7e",
"status": "done",
"row_count": 14,
"usage": {
"credits_charged": 140,
"document_chars": 13480,
"chunk_count": 3,
"duration_seconds": 782
}
}
job.failed
{
"event": "job.failed",
"job_id": "0a77e3f2-9b61-4c05",
"batch_id": "8f2c1e40-91a3-4b7e",
"status": "failed",
"error": {
"code": "no_captions",
"message": "no caption track"
},
"credits_charged": 0
}
NOTE
No rows travel in the payload. On batch.completed, read GET /v1/{batch_id} to collect them. That 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 in Unix seconds. 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));
}

Each endpoint has its own secret, shown on the webhooks screen. Rotating it there keeps the previous one valid for 24 hours, so a deploy has time to catch up.

Deliveries also carry a Web Bot Auth signature (an RFC 9421 HTTP message signature in the Signature, Signature-Input and Signature-Agent headers), which lets a firewall or CDN that supports it, Cloudflare included, confirm a request really came from TubeExtract without knowing your endpoint’s secret. The public key is at /.well-known/http-message-signatures-directory. The HMAC above is still what verifies a payload is intact and meant for your endpoint.

CAREFUL
A webhook_url passed on POST /v1 is registered as an endpoint the first time it is used, with its own secret. From then on it receives every event in the workspace, not only the ones for that call. Remove it on the webhooks screen when you no longer want it.

Retries

Anything other than a 2xx is retried. A delivery gets five attempts in all: immediately, then 30 seconds, 2 minutes, 10 minutes and 30 minutes after each failure, about 45 minutes end to end. Each delivery, with its attempt count and last response code, is listed on the webhooks screen, where a failed delivery can be replayed by hand without re-running the extraction.

CAREFUL
Deliveries can arrive more than once. Deduplicate on the event name together with job_id, or batch_id for batch.completed.
Was this page useful?Tell us what was missing