Webhooks
Receive signed real-time notifications when Automation API jobs finish without polling.
Webhooks allow your application to receive real-time HTTP callbacks when video processing finishes. Instead of repeatedly polling GET /v1/automation-jobs/:id, configure a webhookUrl and tscaps will send a POST request the moment the job settles.
Webhooks trigger once per job upon reaching any terminal state:
succeeded: The video finished rendering and the download link is ready.failed: Processing encountered an error (payload includesfailureCodeandfailureReason).cancelled: The job was cancelled.
Configuring webhooks
Provide a webhookUrl property when calling POST /v1/automation-jobs:
{
"videoUrl": "https://example.com/reel.mp4",
"templateId": "mira",
"webhookUrl": "https://api.yourdomain.com/webhooks/tscaps"
}
The URL must be publicly accessible and use HTTPS. Webhook endpoints pointing to localhost, internal private IPs, or non-HTTPS URLs are rejected at job creation.
Delivery headers
Every webhook request sent by tscaps includes the following HTTP headers defined by the Standard Webhooks specification:
| Header | Description |
|---|---|
webhook-id |
Unique delivery identifier for this event notification. |
webhook-timestamp |
Unix epoch timestamp (in seconds) when the delivery was dispatched. |
webhook-signature |
Cryptographic HMAC-SHA256 signature used to verify that the message originated from tscaps. |
Verifying webhook signatures
Always verify webhook signatures before processing event payloads. Verifying ensures:
- Authenticity: The request genuinely originated from tscaps and was signed with your account’s secret.
- Integrity: The payload was not tampered with in transit.
- Replay prevention: Old intercepted requests cannot be replayed against your server.
Retrieve your account’s webhook signing secret from your Webhooks dashboard.
Important: Always verify the raw request body. Do not verify re-serialized JSON objects, as whitespace and key ordering differences will cause signature verification to fail.
import express from "express";
import { Webhook } from "standardwebhooks";
const app = express();
const signingSecret = process.env.TSCAPS_WEBHOOK_SECRET;
// Preserve the raw body buffer for verification
app.post("/tscaps-webhook", express.raw({ type: "*/*" }), (req, res) => {
const webhook = new Webhook(signingSecret);
let event;
try {
// Verifies raw body buffer and delivery headers
event = webhook.verify(req.body, req.headers);
} catch (err) {
console.error("Webhook signature verification failed:", err.message);
return res.status(400).send("Invalid signature");
}
// Handle job outcome
if (event.status === "succeeded") {
console.log(`Video rendered: ${event.downloadUrl}`);
} else if (event.status === "failed") {
console.error(`Job failed: ${event.failureCode} - ${event.failureReason}`);
}
// Acknowledge receipt
res.sendStatus(200);
});
import os
from fastapi import FastAPI, Header, HTTPException, Request
from standardwebhooks import Webhook
app = FastAPI()
signing_secret = os.environ["TSCAPS_WEBHOOK_SECRET"]
@app.post("/tscaps-webhook")
async def receive_webhook(request: Request):
# Read the raw unparsed body
raw_body = await request.body()
headers = dict(request.headers)
webhook = Webhook(signing_secret)
try:
event = webhook.verify(raw_body, headers)
except Exception as err:
raise HTTPException(status_code=400, detail="Invalid signature")
if event.get("status") == "succeeded":
print(f"Video rendered: {event.get('downloadUrl')}")
elif event.get("status") == "failed":
print(f"Job failed: {event.get('failureCode')} - {event.get('failureReason')}")
return {"received": True}
Event payload
The webhook payload carries the exact same structure as the GET /v1/automation-jobs/:id status endpoint, allowing a single parser to handle both polling and webhook responses.
{
"id": "9f2c48e1-5b7a-4a29-8874-9dfc8227b610",
"status": "succeeded",
"phase": "succeeded",
"templateId": "mira",
"quotedSeconds": 74,
"format": "mp4",
"quality": "high",
"resolution": { "width": 1080, "height": 1920 },
"percent": null,
"downloadUrl": "https://storage.tscaps.io/outputs/9f2c48e1-...?token=...",
"outputExpiresAt": "2026-09-19T10:14:00.000Z",
"failureReason": null,
"failureCode": null,
"settledAt": "2026-09-12T10:12:52.000Z",
"createdAt": "2026-09-12T10:11:38.000Z"
}
Delivery and retry rules
- Return a 2xx status: Your server must respond with HTTP
200(or any 2xx status code) within 10 seconds. Non-2xx responses and timeouts are treated as delivery failures. - Automatic retries: If your endpoint fails or times out, tscaps retries delivery several times using exponential backoff over a 24-hour period.
- Idempotency: Because retried messages may deliver a payload you have already received, ensure your receiver is idempotent. Track processed job
idvalues in your database to avoid duplicating post-processing actions. - No redirects: Webhook requests do not follow HTTP redirects (301/302). Deliveries are made directly to the exact URL specified in
webhookUrl.