Skip to main content
Video generation and async image generation run in the background. Instead of polling 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

  1. Point a job at your endpoint: pass callback_url on the request, or save a default webhook URL for the project.
  2. Opper runs the job and writes its final status, as it always does.
  3. Opper POSTs a signed event (video.completed, image.failed, and so on) to your URL, and retries it if your endpoint does not answer 2xx.
  4. Your endpoint verifies the signature, answers 2xx within 10 seconds, then downloads the result from data.url.

Choose where events go

There are two ways to get a webhook. A callback_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.

A default URL for the project

Save a default webhook URL once, and every async job in the project that sets no callback_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/settings with a key of the project. The key’s owner must be an organization admin or owner, otherwise the call is a 403.
A saved default takes effect right away: from then on, every async video and image job in the project that sets no callback_url sends its webhook there. Have your endpoint ready (and send a test event) before you save it.

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 a 400 that names the rule.
  • https only, 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 .internal and .local names.

The event

Opper sends the event after the job’s final status is written, so a GET 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.
Answer first, work later. Queue the event (or hand it to a background task) and return 2xx straight away; downloading a video inside the handler can outrun the 10 second limit and trigger a retry.

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/secret with 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 with POST /v3/webhooks/secret/rotate (the key’s owner must be an organization admin or owner, otherwise 403).
For 24 hours after a rotation (until 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 a 2xx 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 under webhook 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 signed webhook.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/test with any key of the project.
The answer is a 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.
Members who are not admins or owners can still read the secret with a project key through 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.
Last modified on November 2, 2026