Media Caption API v1
Webhooks
Configure job-level webhook delivery for final bulk job events.
Supported events
job.completedjob.completed_with_errorsjob.failed- Only job-level events are sent in API v1. Item-level transcription events are not sent.
Payload
{
"id": "evt_job_01jz7m91bxkpbqha0wt8whm76r_job.completed",
"type": "job.completed",
"eventId": "evt_job_01jz7m91bxkpbqha0wt8whm76r_job.completed",
"eventType": "job.completed",
"createdAt": "2026-05-11T10:00:25.000Z",
"apiVersion": "2026-05-11",
"data": {
"jobId": "job_01jz7m91bxkpbqha0wt8whm76r",
"jobType": "public_transcripts_bulk",
"status": "completed",
"progress": 100,
"itemCount": 1,
"completedCount": 1,
"failedCount": 0,
"creditsFrozen": 1,
"creditsCharged": 1,
"creditsReleased": 0,
"items": [
{
"itemId": 101,
"externalId": "lesson-001",
"status": "completed",
"transcriptionId": "tr_3f6e5c6a-0d31-4f8c-9b1d-7f3b9e6a2c11"
}
]
}
}Signatures
- Verify the raw request body before parsing JSON.
- Compute
HMAC-SHA256(signingSecret, timestamp + "." + rawBody)and compare it to thev1signature with a constant-time comparison. - Reject stale timestamps. Five minutes is a reasonable default.
- Treat
MediaCaption-Event-Idas an idempotency key because retries can send the same event more than once.
MediaCaption-Event-Id: evt_...
MediaCaption-Timestamp: 1778493622
MediaCaption-Signature: v1=hex_hmac_sha256
signed_payload = "{timestamp}.{raw_request_body}"import crypto from "node:crypto";
function verifyMediaCaptionSignature({
rawBody,
signatureHeader,
timestampHeader,
signingSecret,
}) {
if (!signatureHeader || !timestampHeader) return false;
const timestamp = Number(timestampHeader);
if (!Number.isInteger(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 5 * 60) return false;
const [version, signature] = signatureHeader.split("=");
if (version !== "v1" || !/^[0-9a-f]{64}$/i.test(signature ?? "")) {
return false;
}
const expected = crypto
.createHmac("sha256", signingSecret)
.update(`${timestampHeader}.${rawBody}`)
.digest();
const received = Buffer.from(signature, "hex");
return (
received.length === expected.length &&
crypto.timingSafeEqual(received, expected)
);
}Retries
Any 2xx response is success. Non-2xx responses and network failures are retried with recorded delivery attempts, up to 3 attempts.