REST API
Webhooks
Get push-based updates when a video completes or fails. Avoid polling.
Event types
video.created— fires when a video is queued and credits have been held.video.completed— fires once the final composed video URL is available. TheoutputUrlin the payload is the deliverable.video.failed— fires when generation fails. Credits are refunded automatically; the payload includeserrorwith a sanitized message.
Register an endpoint
Add a webhook at Settings → Webhooks. You'll see a signing secret once — copy it before closing the dialog. You can reveal it again any time, but rotating it requires deleting and recreating the webhook.
Production endpoints must use HTTPS. We block deliveries to private ranges (10.*, 192.168.*, loopback) — webhooks should hit your public endpoint, not your own infra.
Payload shape
Every delivery is a JSON POST with these headers:
X-UnrealUGC-Delivery-Id— stable id for idempotencyX-UnrealUGC-Event— event nameX-UnrealUGC-Timestamp— Unix secondsX-UnrealUGC-Signature— HMAC-SHA256 hex
{
"id": "wd_2NPj...",
"event": "video.completed",
"created": 1747657281234,
"data": {
"video": {
"id": "cv1jzn4a2k...",
"status": "COMPLETED",
"title": "Skincare routine that just works",
"outputUrl": "https://media.unrealugc.com/videos/.../final.mp4",
"creditsCharged": 280,
"creditsFinal": 280,
...
}
}
}Verifying signatures
The signature is HMAC-SHA256 over {deliveryId}.{timestamp}.{rawBody}. Reject any request older than 5 minutes (Standard Webhooks recommendation).
import crypto from "node:crypto";
export function verifyUnrealUgcWebhook(req, rawBody, secret) {
const id = req.headers["x-unrealugc-delivery-id"];
const ts = req.headers["x-unrealugc-timestamp"];
const sig = req.headers["x-unrealugc-signature"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${id}.${ts}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(sig, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hmac, hashlib, time
def verify(headers, raw_body, secret):
delivery_id = headers["X-UnrealUGC-Delivery-Id"]
ts = headers["X-UnrealUGC-Timestamp"]
sig = headers["X-UnrealUGC-Signature"]
if abs(time.time() - int(ts)) > 300:
return False
signed = f"{delivery_id}.{ts}.{raw_body}".encode()
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)Retries & backoff
Non-2xx responses are retried with exponential backoff: 1m → 5m → 30m → 2h → 12h → 24h (~6 attempts, ~38h total before giving up).
We auto-disable a webhook after 20 consecutive failures. The dashboard shows the failure counter so you can fix the endpoint and re-enable it. Re-enabling resets the counter.
Make your handler idempotent — we may deliver the same event more than once. Use X-UnrealUGC-Delivery-Id as the dedupe key.