Skip to content

Webhooks

A webhook tells your server the moment a narration or video finishes, so you never have to poll for it. Pass webhookUrl when you create one:

Terminal window
curl -X POST "https://api.scenema.ai/v1/videos" \
-H "Authorization: Bearer $SCENEMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"audioId": "proj_4559d9dd5dc8d53", "style": "pure-ink-whiteboard", "prompt": "Show sunlight scattering in the sky.", "webhookUrl": "https://example.com/hooks/scenema"}'

The URL must be https on a public host.

A default URL for every request

Set a default webhook URL once and leave webhookUrl out of your requests: open API in the Scenema sidebar and save it under Webhooks. A narration or video that names no webhookUrl sends its webhook there. A request’s own webhookUrl always wins, and clearing the default sends none. The workspace owner and admins can change it.

Events

Scenema sends one event when the work finishes:

Event When
narration.completed The narration’s audio is ready.
narration.failed The narration could not be made.
video.completed The MP4 is ready.
video.failed The video could not be made.

The request

A POST with a JSON body and three headers:

POST /hooks/scenema HTTP/1.1
Content-Type: application/json
webhook-id: msg_3f9a1c2b7d4e5f60a1b2
webhook-timestamp: 1793180411
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
{"type": "video.completed", "timestamp": "2026-10-28T09:20:11.200Z", "data": { ...the video, as GET /v1/videos/{id} returns it... }}

data is the same object as GET, so one handler serves both. Its links to the MP4 and captions last an hour: fetch the video again for fresh ones.

A video can finish more than once. A retry and each change (a new aspect ratio, a new audio track) send video.completed or video.failed again when they end, each as its own delivery with its own webhook-id. Read aspectRatio, audioTrack and attempt in data to tell them apart.

Answer with any 2xx status within 10 seconds. Do slow work after you answer.

Verify the signature

Scenema signs every webhook per the open Standard Webhooks spec, so any Standard Webhooks library verifies it. Your workspace’s signing secret comes from GET /v1/webhooks/secret:

Terminal window
curl -H "Authorization: Bearer $SCENEMA_API_KEY" "https://api.scenema.ai/v1/webhooks/secret"
# {"secret": "whsec_..."}
// npm install standardwebhooks
import { Webhook } from 'standardwebhooks';
const wh = new Webhook(process.env.SCENEMA_WEBHOOK_SECRET);
// rawBody: the request body exactly as received, before JSON parsing.
const event = wh.verify(rawBody, {
'webhook-id': req.headers['webhook-id'],
'webhook-timestamp': req.headers['webhook-timestamp'],
'webhook-signature': req.headers['webhook-signature'],
});
# pip install standardwebhooks
from standardwebhooks.webhooks import Webhook
wh = Webhook(os.environ["SCENEMA_WEBHOOK_SECRET"])
event = wh.verify(raw_body, request.headers)

The libraries also refuse a timestamp more than five minutes off, which stops a captured webhook from being replayed later. To verify by hand: the signature is v1, and the base64 HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<raw body>, keyed with the base64 decoded part of the secret after whsec_.

POST /v1/webhooks/secret/rotate makes a new secret. For the next 24 hours every webhook carries two signatures, one with the new secret and one with the old, so you can switch your receiver over without missing any: Standard Webhooks libraries accept a match with either. After that, only the new secret signs.

Retries

When your server does not answer 2xx within 10 seconds, Scenema tries again after:

Attempt Wait before it
2 30 seconds
3 5 minutes
4 30 minutes
5 2 hours
6 5 hours
7 10 hours
8 10 hours

That is about 27 hours in all. After the eighth attempt the delivery is failed. A redirect counts as a failed attempt: give the final URL.

See and retry deliveries

Every delivery, with the body it sent (payload) and every attempt (status code, the start of your answer, the time it took, or why there was no answer), is in the request that created the work: GET /v1/requests/{id} returns them under webhooks. The API page in Scenema shows the same, with a retry button on each delivery. While a delivery is pending, nextAttemptAt says when the next attempt goes out, and nextAttemptIn how long that is from now (“4 min 55 s”, “2 h 5 min”).

To send one again now, for example after fixing your endpoint:

Terminal window
curl -X POST -H "Authorization: Bearer $SCENEMA_API_KEY" \
"https://api.scenema.ai/v1/webhooks/deliveries/msg_3f9a1c2b7d4e5f60a1b2/retry"

A manual retry carries the same webhook-id and does not change the schedule. See Usage and requests.