A webhook pushes the result to your server when a task ends, so you do not poll. Dramatis Studio sends one event for each task, when it reaches a final status and its result links are ready.
The signing secret
Events are signed with your account's secret, which looks like whsec_ followed by characters. Create it in the console under Webhooks. It is shown once, and creating another replaces it. Without a secret, a task may not carry a webhook_url and you may not register an address: Dramatis Studio never sends an unsigned event.
Where events go
Every task event has one delivery for each destination:
- every address your account has registered in the console, up to 16;
- the task's own
webhook_url, when it has one.
An address record holds id, url and created_at, and nothing more. The first release has no pause (remove the address and add it again) and no per-task opt-out. A webhook_url that is also a registered address gets two deliveries with the same webhook-id. Deduplicate on that id.
An address must be HTTPS on port 443, with no credentials in it, and must resolve only to public addresses. Redirects are not followed.
The event
POST <your address>
|
Content-Type: application/json
|
User-Agent: Opsis-Webhook/1
|
webhook-id: evt_01k3m8…
|
webhook-timestamp: 1793610741
|
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
|
|
{
|
"id": "evt_01k3m8…",
|
"type": "task.succeeded",
|
"created_at": "2026-11-02T09:12:21Z",
|
"data": { "…the task, with fresh result links…" }
|
} |
There are two event types, task.succeeded and task.failed. The reason for a failure, a cancel and a timeout included, is in error.code of the task in data.
The signature
The three headers follow the Standard Webhooks format. The signature is HMAC-SHA256, in base64, over webhook-id, a dot, webhook-timestamp, a dot and the raw body, keyed with the secret after the whsec_ prefix, base64-decoded. Refuse a webhook-timestamp more than 5 minutes from your own clock.
The packages are not published yet. The samples show the signature check they are meant to make, on the raw body exactly as received:
import { verifyWebhook } from "opsis-sdk"
|
|
// In your request handler. `rawBody` is the body as received, before any JSON parsing.
|
const event = verifyWebhook({
|
secret: process.env.OPSIS_WEBHOOK_SECRET,
|
headers: request.headers,
|
body: rawBody,
|
})
|
console.log(event.type, event.data.id) |
import os
|
|
from opsis.webhooks import verify_webhook
|
|
# In your request handler. `raw_body` is the body as received, before any JSON parsing.
|
event = verify_webhook(os.environ["OPSIS_WEBHOOK_SECRET"], request.headers, raw_body)
|
print(event["type"], event["data"]["id"]) |
A body that was parsed and written out again will not verify. The check throws when a header is missing, the timestamp is out of range or no signature matches.
Answer and retries
Answer with any 2xx within 30 seconds. Dramatis Studio reads at most 64 KiB of your answer and only the status counts.
Each delivery is tried up to 6 times: at once, then after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. A retry after the content was deleted still sends the event, with input and output null. The console shows each delivery with its destination, its status and your response code.
Duplicates and order
webhook-id is the same on every retry and on every destination of the event. Deduplicate on it. Order is not guaranteed. The task is the truth: you may always read it again with GET /v1/tasks/{id}.
Video tasks
The event of a video task has the same type, signing and retries as any other. Its data is the task with output.video in place of output.images, and the links in it are fresh. Video has a receiver that checks the signature and downloads the clip.