Documentation
Webhooks
Signed notifications when a video is ready, failed or was canceled.
Set an endpoint URL in API and webhooks (Growth and Scale plans). Pixamake sends a POST request with a JSON body for these events:
| Event | When |
|---|---|
job.succeeded |
The clean video is ready |
job.failed |
Processing failed (credits returned) |
job.canceled |
The job was canceled |
webhook.test |
Sent with the Send test event button |
Payload
{
"id": "evt_9sKq2mT4nVb7Lx3Rp8Wc",
"object": "event",
"type": "job.succeeded",
"created_at": "2026-10-04T13:09:58.012Z",
"data": { "object": { "id": "job_3kT9xQ2vLm8RfZp1Wq7a", "object": "job", "status": "succeeded", "result": { "url": "https://..." } } }
}data.object is the full job object. Its result.url expires after one hour: download right away, or fetch the job later for a fresh link.
Verify the signature
Each request has a Pixamake-Signature header:
Pixamake-Signature: t=1791119398,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
v1 is the hex HMAC SHA-256 of "{t}.{raw body}" with your signing secret (whsec_...). Compare in constant time and refuse timestamps older than 5 minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: string, header: string, secret: string) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return abs(time.time() - int(parts["t"])) < 300 and hmac.compare_digest(expected, parts.get("v1", ""))Always verify against the raw request body, before parsing the JSON.
Delivery and retries
- Answer with any
2xxstatus within 15 seconds. Do the heavy work after answering. - Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours.
- Deliveries can arrive more than once or out of order: use the event
idand the jobstatusto process each change once. - Redirects are not followed, and the URL must be public (
httpsin production). - You can rotate the signing secret at any time from the dashboard.