Overview
Every create endpoint, andPOST /v1/render, takes an optional webhook URL. When the video finishes, or the project fails, Hooked sends a signed POST with JSON to that URL (Content-Type: application/json).
The URL you pass on create belongs to that project and is called for its automatic render. A render started later with POST /v1/render calls only the webhook passed in that request, and a render started from the dashboard editor calls none.
You can also register team endpoints in the dashboard under Settings → Webhooks. Each one picks the events it wants and receives them for every project and render in the team, whether it was started from the API or the dashboard. If you pass no webhook and register no endpoint, nothing is sent and you can poll GET /v1/project/{projectId} instead.
How it works
1
Pass a webhook URL
2
Hooked generates the video
The create call answers right away with a
projectId. Generation takes a few minutes depending on the format.3
Hooked calls your URL
Once the video is rendered, or when the project fails, your URL gets a
POST with the result.4
Verify the signature, then answer 2xx quickly
Respond with any 2xx status within 10 seconds. Do the slow work (downloading, emailing) after you answer.
URL requirements
https://only. Anything else is rejected at create time with400 webhook: Must be a valid HTTPS URL.- A public host. A URL that names
localhost, a private IP, a*.local/*.internalhost or a cloud metadata address is refused at create time with the same400. A hostname that only resolves to a private address is accepted on create, but the call is never sent. - Up to 500 characters.
- Redirects are not followed. A
3xxcounts as a failed attempt, so give the final URL.
Payloads
The body has noevent field (it is the same body Hooked has always sent). Read the top-level status, COMPLETED or FAILED, or the Hooked-Event header.
Headers and events
Every call carries these headers:
The per-request
webhook URL of a project gets the first three; the one of a media call gets the media.* pair (see Media webhooks). A team endpoint gets only the events it subscribed to.
Video completed
Render failed or timed out
message is Video rendering failed, Video rendering timed out or Video failed.
Project failed before it had a video
If the project fails while it is still generating (a provider error, running out of credits mid-way), there is no video yet, sovideoId is null:
message is the project’s own failure message. Credits charged for a failed project are refunded automatically.
Fields
string
COMPLETED or FAILED.string
A human-readable summary. On failures, the reason.
object
Media webhooks
POST /v1/media/generate/image, POST /v1/media/generate/video and POST /v1/media/import also take an optional webhook (same URL requirements) and metadata (any JSON object, up to 5 KB). The URL is told once, when the media becomes COMPLETED or FAILED, with the event media.completed or media.failed, signed with the same request signing secret as project webhooks.
Team endpoints subscribed to media.completed / media.failed get the same body for every library generation, import and upload of the team, from the API or the dashboard.
urlis the file, signed and expiring like every Hooked link;""whenFAILED. Get Media gives a fresh one.typeisimageorvideo.erroris why it failed (nullwhenCOMPLETED); Get Media shows the same text. A failed generation’s credits are refunded automatically.metadatais the object you sent, ornull.
FAILED and media.failed is sent then. Poll Get Media as a backup when no webhook arrives.
Retries
Hooked tries each delivery up to 3 times in a row, with no delay between attempts. An attempt fails when your endpoint answers anything other than 2xx, does not answer within 10 seconds, or the connection fails. After the third failed attempt the notification is dropped. Because of that:- Answer fast and do the work afterwards.
- Do not rely on the webhook alone. If your endpoint was down, poll
GET /v1/project/{projectId}to catch up. - Handle duplicates. If your endpoint processed a call but answered too late, the same payload arrives again with the same
Hooked-Deliveryid. Use that id, orvideoId(projectIdwhenvideoIdis null), to ignore repeats.
Verify the signature
Every call is signed with HMAC-SHA256, so you can tell that it came from Hooked and was not changed on the way.Where to find the secret
In the dashboard, open Settings → Webhooks:- Signing secret signs the calls to the
webhookURL you pass in API requests. There is one per team, created the first time it is needed. - Each team endpoint has its own secret, shown on its row.
whsec_. Click the eye icon to reveal one. Rotate creates a new secret at once and the old one stops working, so update your server right after.
How the signature is built
v1 is the hex HMAC-SHA256 of "<t>.<raw body>" using the secret as the key. To check it:
- Read
tandv1from the header. - Reject the call if
tis more than 5 minutes away from your clock (stops replays). - Compute the HMAC over
t, a., and the raw request body exactly as received. Do not parse and re-serialize the JSON first: any change breaks the signature. - Compare it with
v1in constant time.
Node.js
Python
Extra checks
- A token in the URL (
?token=...) is no longer needed, but you can still add one as a second check. - Do not trust the payload blindly for sensitive actions: confirm it with
GET /v1/project/{projectId}using your API key. This also gives you a fresh download link. - Use your own correlation ids in
metadataso you know which of your records a call belongs to.
Implementing an endpoint
Node.js (Express)
Python (FastAPI)
Next.js route handler
Testing
Local development
Hooked can only reach public HTTPS URLs, so expose your local server with a tunnel such as ngrok:Send yourself a test payload
Sign a sample body with your secret the same way Hooked does:Common issues
The webhook never arrives
The webhook never arrives
- Check the URL is
https://, public, and does not redirect. - Make sure your endpoint answers 2xx within 10 seconds.
- Check your server logs for three quick attempts close together: that means it answered with an error.
- Poll
GET /v1/project/{projectId}: if the project is stillprocessing, there is nothing to send yet.
The signature does not match
The signature does not match
- Verify the raw body. Frameworks that parse JSON first (for example
express.json()) change the bytes. - Use the right secret: the Signing secret for the
webhookURL in your requests, the endpoint’s own secret for a team endpoint. - After a Rotate, the old secret stops working at once.
- Check your server clock: calls more than 5 minutes off are rejected by the snippets above.
The same payload arrives more than once
The same payload arrives more than once
Your endpoint answered late or with an error and Hooked retried. Deduplicate on
videoId / projectId.The download link stopped working
The download link stopped working
Links can expire. Call
GET /v1/project/{projectId} and use video.url for a fresh one.Next steps
Error Handling
Handle API errors
Best Practices
Production-ready patterns