AI video API webhooks: secure production pattern


Quick answer
Verify the signature against the untouched request body before trusting a webhook. Then validate its project identity, commit a durable receipt and work item, and acknowledge with 2xx. Make downstream effects safe to repeat. Keep a status-reconciliation path because delivery can fail and canceled projects do not emit a cancellation event.
A webhook receiver is an authenticated ingestion boundary. Its immediate job is to decide whether a delivery is genuine and whether your application has safely taken responsibility for it. Downloading media, sending customer notifications and updating an editor belong behind that boundary. This guide uses Magic Hour’s current webhook contract, checked October 23, 2026, and labels the database and worker pattern below as application design recommendations.
The exact Magic Hour delivery contract
Start with the Webhook Quickstart to register your public HTTPS endpoint and selected events in the Developer Hub. Save the generated webhook secret in server-side secret storage. It is the key for authenticating incoming deliveries; it is not the API bearer token used for outgoing project requests.
The current envelope contains type and payload. For video, the subscribed types are video.started, video.completed and video.errored. Read the project identifier from payload.id, not from a made-up project_id field. The contract does not expose a separate event ID: do not build a database constraint around event.id unless a future schema actually provides it.
Contract field | Meaning for the receiver |
|---|---|
magic-hour-event-signature | HMAC-SHA256 signature represented as hexadecimal. |
magic-hour-event-timestamp | Unix seconds; use the supplied string in the signed message. |
type | Event name, such as video.completed. |
payload.id and payload.status | Project identity and the project state included in the notification. |
payload.error | Nullable object with code and message for a project error. |
payload.downloads | Array containing output url and expires_at values; do not depend on the deprecated singular download field. |
A reduced illustrative body is {"type":"video.completed","payload":{"id":"example-project","status":"complete","error":null,"downloads":[]}}. This shows the envelope and routing fields, not a complete schema-valid delivery or an observed output. Real payloads contain additional project fields, and a completed project with an empty downloads array needs reconciliation before your application offers a file.
Verify raw bytes before parsing JSON
Use the secure-handler reference for the exact signature recipe. The message is the timestamp header, a literal period, then the raw JSON body as received. Compute HMAC-SHA256 with the webhook secret and compare the resulting hexadecimal signature using a constant-time comparison.
For example, if the timestamp text is 1729314984 and the received body begins {"type":"video.completed", the signed message begins 1729314984.{"type":"video.completed". This is a concatenation example, not a reusable signature. Use the full original body, including whitespace and property order. Parsing JSON and serializing it again can change that representation, so the recomputed signature may no longer match.
Capture the body before middleware consumes or rewrites it. In Express, use route-appropriate raw or text handling before a JSON parser; in a framework that exposes request-body bytes, read them once and retain them for verification. A proxy or framework adapter must preserve the representation that reaches the verifier. Parse and schema-check only after authentication succeeds.
Reject missing headers, malformed timestamps and malformed signatures cleanly. Validate that the signature is the expected hexadecimal representation before decoding or comparing buffers, and check equal lengths before calling an API that requires equal-length inputs. Use the timestamp string exactly as supplied for the HMAC calculation rather than converting it to a number and reformatting it.
The official security guide recommends a five-minute freshness window. Treat that as a receiver policy, not a job-completion deadline. Check past and future skew, keep the host clock synchronized, and measure receipt time when the HTTP request arrives. A worker that runs much later should use the durable record of verification at ingestion; it should not reinterpret an old receipt as a new delivery.
Validate identity and scope after authentication
A valid signature establishes that the body matches a holder of the configured secret. It does not establish that every field is appropriate for your particular customer action. Require a supported type and a well-formed payload.id. Match that project to a stored submission for the correct account or tenant before granting access to a result or altering an order.
Use separate endpoint configuration for environments or otherwise record which webhook configuration authenticated the delivery. An event for an unfamiliar project should be durably quarantined for review, not attached to whichever customer is currently signed in. Unknown event types should follow an explicit ignore or quarantine policy so a new upstream event cannot accidentally trigger completion logic.
Commit the receipt before acknowledging
The production-handler guide requires saving verified events before 2xx. Network failures, timeouts and non-2xx responses trigger scheduled retries after 1, 2, 4, 8 and further minutes, capped at 60 minutes between attempts. Events expire 24 hours after creation, rather than 24 hours after the first failure. Each attempt receives a new timestamp and signature. These are scheduled delays, not guaranteed arrival times; worker completion and customer notification remain separate.
Use a durable inbox record for the received type, project ID, receipt time, verification result and processing state. Retain only the payload information your support and recovery process needs, with access controls and a retention policy. Keep secrets, bearer tokens and full signed download URLs out of routine logs. Store sensitive payloads in controlled storage when they are necessary.
Commit the inbox row and durable work item together, or use a transactional outbox that a worker can discover after commit. An in-memory task started just before returning success can disappear on a process restart. If persistence fails, do not claim successful ingestion: return a failure response and let delivery recovery remain possible.
After a successful commit, return a small success response promptly. A 200 response with {"message":"accepted"} fits the documented response shape. Here, accepted means the application owns the event; it does not mean media was downloaded or a user was notified. Do not keep the HTTP request open while performing those slower effects.
Make effects repeatable without inventing an event ID
A database commit can succeed while the HTTP acknowledgment is lost. The sender can then deliver again, so your worker must tolerate another receipt. A fingerprint of the raw body can identify byte-identical deliveries in your inbox, but it is not a platform event identity. Payloads may differ in nonessential fields, including refreshed download information.
Protect the business effect independently. For a single completion notification, an application-owned unique key such as account plus project ID plus notification kind prevents two workers from creating two notification tasks. Store the effect’s state and retries durably. Choose the key for the action: a generic project-ID-only key would wrongly suppress later event types for the same project.
Exactly-once execution across your database and an external email or storage service requires those systems’ own guarantees. A local unique row alone cannot resolve every crash after a remote side effect succeeds but before your worker records success. If the downstream service supports an idempotent operation, use it; otherwise retain an uncertain state for reconciliation instead of promising perfect deduplication.
Handle late events with a current project read
Do not infer a sequence number from the event timestamp or treat payload.created_at as the event time. Neither provides a verified ordering contract. If a late video.started event arrives after your application recorded completion, avoid regressing the visible state. Reconcile with GET https://api.magichour.ai/v1/video-projects/{id}, using the stored project ID and server-side API bearer token.
The current API states are draft, queued, rendering, complete, error and canceled. The video.started notification corresponds to rendering. Cancellation has no matching webhook event, so a receiver-only design cannot detect every canceled job. Periodic, bounded reads for unresolved projects provide a recovery path; a missed callback must not cause a new generation request.
For request-level failures after that read, use the Get video details reference. The receiver should record the read failure and retry or escalate its own work; it should not conflate an unavailable status response with a failed video.
Illustrative completion handling walkthrough
Suppose your application already stored example-project for a customer’s video request. A video.completed delivery arrives. The following sequence illustrates the proposed receiver behavior; it is not a production run, benchmark or guarantee.
Stage | Application action | Evidence retained |
|---|---|---|
Ingest | Capture headers and raw body; verify HMAC and freshness. | Receipt time and bounded verification reason. |
Commit | Match the project; save inbox receipt and unique completion work item. | Inbox ID, project ID and pending work state. |
Acknowledge | Return 200 only after the durable commit. | Acknowledgment outcome, separate from worker outcome. |
Process | Read current project details, then handle the existing output if available. | Current status and effect-specific progress. |
Repeat delivery | Keep the new receipt if useful; reuse or skip the already-owned effect. | Duplicate decision without a second customer notification. |
Worker failure | Retain pending or uncertain work for recovery. | Attempt reason and next action; no replacement generation. |
If the database is unavailable at ingest, return failure and record the operational incident through a separate reliable log path. If the download later fails, retry the download or refresh the project details; webhook delivery has already been acknowledged. If a completion and an earlier start notification conflict, read current state and retain both receipts for diagnosis.
Receiver operations checklist
Before enabling production traffic, confirm that the registered URL reaches the intended handler over HTTPS, the stored secret belongs to that configuration, and body parsing occurs after verification. Confirm that missing or invalid security headers cannot enter the trusted inbox. Keep the timestamp policy explicit and inspect clock skew before widening it.
Review the durability boundary: can pending work be found after a restart, and can two workers claim the same effect without duplicating it? Review unknown-project and unknown-event handling. Document what triggers reconciliation and who handles a persistent queue failure. Distinguish delivery failures, ingestion failures and worker failures in monitoring.
During an incident, collect the webhook configuration identifier, receipt time, event type, project ID, verification category, persistence result and worker state. A rise in signature mismatches calls for checking the secret and raw-body path; a rise in stale timestamps calls for checking clock and ingress delay. Repeated successful acknowledgments with stalled workers calls for repairing the consumer, not changing the sender’s signature settings.
For event-specific payload examples, consult the event-types reference. For a concrete automation implementation, see our image-to-video n8n workflow; its job submission remains separate from this receiver’s authentication and acknowledgment boundary.
Original documentation sources: https://docs.magichour.ai/integration/webhook/overview; https://docs.magichour.ai/integration/webhook/secure-handler; https://docs.magichour.ai/integration/webhook/create-handler; https://docs.magichour.ai/integration/webhook/event-types
Implement the verified receiver boundary
Register the endpoint, follow the raw-body signature reference, and acknowledge only after durable ingestion.
Open webhook setupFrequently asked questions
Use webhooks for normal event delivery and bounded GET reconciliation for unresolved or conflicting states. Cancellation has no matching webhook event, so a current project read is still needed to detect canceled jobs.
A failed or lost acknowledgment can lead to another delivery. Keep a durable inbox and make business effects safe to repeat; the current envelope has no separate event ID.
Return 2xx promptly after authentication, validation and a durable receipt/work commit. A 200 JSON response with a message field fits the reference. Do not acknowledge successful ingestion when persistence failed.

Continue Reading

How to evaluate AI media APIs: total time, cost and reliability

6 AI APIs for a weekend SaaS: architecture and launch checks

Image-to-video in n8n: reliable Magic Hour API workflow

Video face swap in n8n: reliable Magic Hour API workflow

