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:
EXPORT_STARTED: Export has actually started (render task created), not just queued.EXPORT_COMPLETED: Export finished successfully.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.
durationSecondsis the real export duration.billedStandardSecondsis 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
2xxonly 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