tscaps.docs
Documentation

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 includes failureCode and failureReason).
  • 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:

  1. Authenticity: The request genuinely originated from tscaps and was signed with your account’s secret.
  2. Integrity: The payload was not tampered with in transit.
  3. 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 id values 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.