status_url until the job ends, you can have Opper send a signed
POST to your endpoint when it does. The event carries the same object the
status poll returns, so one handler covers both.
Webhooks are for async jobs only:
POST /v3/videos,
and POST /v3/images with
"async": true. A synchronous image request that sets callback_url is
refused with a 400 (set async: true).How it works
- Point a job at your endpoint: pass
callback_urlon the request, or save a default webhook URL for the project. - Opper runs the job and writes its final status, as it always does.
- Opper POSTs a signed event (
video.completed,image.failed, and so on) to your URL, and retries it if your endpoint does not answer2xx. - Your endpoint verifies the signature, answers
2xxwithin 10 seconds, then downloads the result fromdata.url.
Choose where events go
There are two ways to get a webhook. Acallback_url on the request always wins.
callback_url on the request
Add callback_url to the request body. That job, and only that job, reports there.
- Video
- Image (async)
A default URL for the project
Save a default webhook URL once, and every async job in the project that sets nocallback_url reports there. A project has one webhook.
- In the platform: open Settings > Webhooks in the Opper platform, choose Add webhook, pick the project and enter the endpoint URL.
- Over the API:
PATCH /v3/webhooks/settingswith a key of the project. The key’s owner must be an organization admin or owner, otherwise the call is a403.
URL rules
Opper checks the URL when you submit the job or save the default, and again before every delivery attempt. A URL that breaks a rule is a400 that names the rule.
httpsonly, on port 443 (or no port).- No credentials (
user:password@) and no#fragment. - At most 2048 characters.
- The host is a public IP address, or a public name that resolves only to
public addresses. Localhost, private, link-local, CGNAT and cloud metadata
addresses are refused, and so are
.internaland.localnames.
The event
Opper sends the event after the job’s final status is written, so aGET of
status_url when you receive it shows the same state.
video.completed
The event carries no prompt and no media bytes.
data.url is a presigned link
that expires after an hour (expires_at). If you download later, GET
status_url for a fresh one.
Event types
A job that stops before it ends (for example, the worker running it went away)
is marked failed with
error_type interrupted, and sends its video.failed
or image.failed event then.
Verify the signature
Every delivery is signed the Standard Webhooks way, so any Standard Webhooks library verifies it. Each request carries three headers:
The HMAC key is the base64-decoded part of the secret after
whsec_. The
libraries handle this, refuse timestamps more than 5 minutes off, and accept a
request when any of its signatures match.
- TypeScript
- Python
The signing secret
Each project has one signing secret (whsec_...) that signs all its webhooks.
Keep it server-side: whoever holds it can sign a request your endpoint accepts.
- In the platform: Settings > Webhooks, open the project’s webhook, then Reveal or Copy the secret.
- Over the API:
GET /v3/webhooks/secretwith any key of the project. The secret is made on first use.
Rotate the secret
Rotate from the webhook’s page in Settings > Webhooks, or withPOST /v3/webhooks/secret/rotate (the key’s owner must be an organization
admin or owner, otherwise 403).
previous_valid_until), every webhook
carries two signatures, one made with the new secret and one with the
previous. Deploy the new secret to your receiver within that window and no
delivery is dropped. Rotating again within the 24 hours retires the previous
secret at once.
Delivery and retries
Answer with a2xx status within 10 seconds. Redirects are not followed.
A webhook gets up to 8 attempts in all, waiting about 1 minute, 5 minutes,
30 minutes, 1 hour, 3 hours, 6 hours and 12 hours between them (about 23 hours
in total). Every attempt has a new
webhook-timestamp and signature, and the
same webhook-id, so make your handler idempotent on webhook-id: you may get
an event more than once.
Check a delivery
The status poll reports the delivery underwebhook once the job has ended:
webhook.status is pending (still trying), delivered or failed.
last_status_code is absent when the last attempt got no answer. The
Settings > Webhooks list in the platform shows each webhook’s delivered,
failed and retrying counts for the last 7 days.
The status poll keeps working whether or not the webhook arrives, so polling
status_url remains a fallback for a job whose webhook failed.Send a test event
Check that your endpoint is reachable and verifies signatures before you rely on it. A test sends one signedwebhook.test event now, with no job and no
retries.
- In the platform: Send a test event on the webhook’s page in Settings > Webhooks.
- Over the API:
POST /v3/webhooks/testwith any key of the project.
200 whether or not your endpoint took the event: delivered
is true for a 2xx, status_code is what your endpoint answered, and
error says what went wrong otherwise. The URL goes through the same
rules as a job’s (400 names the rule), and a project can send
10 test events a minute (429 past that).
Settings > Webhooks
The Settings > Webhooks page in the Opper platform is where admins and owners manage webhooks without the API:- The list shows every project with a webhook: its URL, whether it is on, and the last 7 days of deliveries (delivered, failed, retrying).
- Add webhook picks a project that has none yet and saves its endpoint URL. It is on at once.
- The webhook’s page edits the URL, reveals, copies and rotates the signing secret, sends a test event, and removes the webhook. Removing clears the URL and keeps the secret.
GET /v3/webhooks/secret.
Reference
callback_url is a field of Generate a video
and Generate an image.
What’s next
Video
Submit a video job and get its result.
Images
Generate images, synchronously or as an async job.