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 2xx status 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 id and the job status to process each change once.
  • Redirects are not followed, and the URL must be public (https in production).
  • You can rotate the signing secret at any time from the dashboard.