Webhooks

Receive export start and result events safely.

Use callbacks when you do not want to rely on continuous polling.

Configure Callback on Task Create

Set these fields in POST /v1/exports:

  • callbackUrl: Destination endpoint.
  • callbackHeaders: Optional static headers.

If callbackUrl is provided but your account has no webhook secret configured, create will fail.

Default Event Flow

When callbackUrl is configured, callbacks follow a fixed flow:

  1. EXPORT_STARTED: Export has actually started (render task created), not just queued.
  2. EXPORT_COMPLETED: Export finished successfully.
  3. EXPORT_FAILED: Export finished with failure.

Payload Contract

Webhook payload uses a fixed event envelope:

{
  "eventType": "EXPORT_COMPLETED",
  "occurredAt": "2026-03-11T13:00:00.000Z",
  "task": {
    "taskId": "565693ff-e120-4326-94f8-5ffe17543101",
    "createdByApiKeyId": "e356f320-bf36-45a4-afae-96bacaab6a45",
    "clientTaskId": null,
    "status": "COMPLETED",
    "progress": 100,
    "error": null,
    "outputUrl": "https://example.com/output.mp4",
    "durationSeconds": 18.4,
    "billedStandardSeconds": 32,
    "chargedCredits": "53.333333333333",
    "callbackUrl": "https://example.com/callback",
    "createdAt": "2026-03-11T12:59:00.000Z",
    "completedAt": "2026-03-11T13:00:00.000Z"
  }
}

The task object is aligned with GET /v1/exports/{taskId} response data.

  • durationSeconds is the real export duration.
  • billedStandardSeconds is the billing unit used for quota and credit charging.

Verify Signature

Every callback includes these headers:

  • X-Indream-Timestamp: Unix timestamp in seconds.
  • X-Indream-Signature: Lowercase hex HMAC signature.

Signature input is exact raw body bytes plus timestamp:

payload = `${timestamp}.${rawBody}`
signature = HMAC_SHA256(webhookSecret, payload)

Use the raw request body for verification. Do not parse and re-stringify JSON before verification.

Node.js Helper

import { parseExportWebhookEvent, verifyExportWebhookRequest } from '@indreamai/client'

const rawBody = await request.text()
const isValid = await verifyExportWebhookRequest({
  webhookSecret: process.env.INDREAM_WEBHOOK_SECRET!,
  rawBody,
  headers: request.headers,
  maxSkewSeconds: 300, // optional
})

if (!isValid) {
  return new Response('invalid signature', { status: 401 })
}

const event = parseExportWebhookEvent(JSON.parse(rawBody))
console.log(event.task.durationSeconds, event.task.billedStandardSeconds)

Python Helper

import json

from indream import verify_export_webhook_request
from indream.types import ExportWebhookEvent

raw_body = request.get_data(as_text=True)
is_valid = verify_export_webhook_request(
    webhook_secret=WEBHOOK_SECRET,
    raw_body=raw_body,
    headers=request.headers,
    max_skew_seconds=300,  # optional
)

if not is_valid:
    return ("invalid signature", 401)

event = ExportWebhookEvent.model_validate(json.loads(raw_body))
print(event.task.duration_seconds, event.task.billed_standard_seconds)

Event Handling Principles

  • Treat callback delivery as at-least-once.
  • Re-fetch the latest task by GET /v1/exports/{taskId} before critical state transitions.

Receiver Endpoint Checklist

  • Verify webhook signature before business processing.
  • Return 2xx only after persistence succeeds.
  • Persist full payload plus receive timestamp.
  • Reject malformed payloads with 4xx.
  • Keep processing timeout short and offload heavy work to async jobs.

Last updated on

On this page