Skip to main content
Video generation is asynchronous by design. This recipe shows the minimum application loop you need for a production-safe integration.

1. Create the job

curl https://api.phaseo.app/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/veo-3.1-lite",
    "prompt": "A cinematic sunrise over a mountain lake",
    "webhook": {
      "url": "https://example.com/api/video-webhook",
      "events": ["video.completed", "video.failed", "video.cancelled", "video.expired"],
      "secret": "whsec_your_signing_secret"
    }
  }'
Store the returned video_id immediately.

2. Poll status until terminal

Your worker or application can poll:
curl https://api.phaseo.app/v1/videos/VIDEO_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
Use polling for your own control loop even if you also enable webhooks. That gives you a direct way to recover if a webhook destination is temporarily unavailable. Persist and poll with the gateway-owned video id. native_video_id, when present, is provider-native correlation metadata; do not use it for AI Stats polling, websocket, cancellation, or content URLs.

3. Consume webhook deliveries

The current async webhook payloads are normalized around:
  • the job identifier
  • lifecycle status
  • delivery status summary
  • recent delivery attempts
  • whether signing is enabled
Design your webhook consumer to:
  1. verify the signature
  2. treat deliveries as retryable and idempotent
  3. fetch the latest job status if the webhook payload and local state disagree
When you configure webhook.secret, AI Stats signs each delivery with:
  • x-ai-stats-timestamp: Unix timestamp in seconds
  • x-ai-stats-signature: hex HMAC-SHA256 of ${timestamp}.${rawBody} using your webhook secret
  • x-ai-stats-event-id: stable event id for this job/event
  • x-ai-stats-event-type: event type such as video.completed
  • x-ai-stats-delivery-key: idempotency key, including progress bucket when applicable
  • x-ai-stats-attempt and x-ai-stats-max-attempts: retry attempt metadata
Verify the signature against the exact raw request body before parsing JSON:
import { verifyAsyncWebhookSignature } from "@ai-stats/sdk";

export async function POST(request: Request) {
  const rawBody = await request.text();
  const ok = verifyAsyncWebhookSignature({
    secret: process.env.AI_STATS_WEBHOOK_SECRET!,
    body: rawBody,
    headers: request.headers,
  });

  if (!ok) {
    return new Response("invalid signature", { status: 401 });
  }

  const event = JSON.parse(rawBody);
  // Persist event.id or the x-ai-stats-delivery-key header before side effects.
  return Response.json({ received: true });
}
Store x-ai-stats-delivery-key or the payload id before side effects. Return any 2xx status only after your durable state has been updated; non-2xx responses are retried with the same event id and an incremented attempt number. Webhook subscriptions accept generic job.* events or matching video.* events. If you omit events, AI Stats subscribes the webhook to terminal job.completed, job.failed, job.cancelled, and job.expired notifications. If you provide events, at least one event must be valid for video jobs; all-invalid or cross-kind-only lists such as ["batch.completed"] are rejected instead of being broadened silently. Webhook callback URLs must use HTTPS. Literal private, loopback, link-local, and wildcard hosts are rejected; http://localhost, http://127.0.0.1, and http://[::1] are accepted only for local development callbacks.

4. Read the final output

When the job reaches a completed terminal state, fetch content from:
GET /v1/videos/{video_id}/content
If your application only needs a download URL, use the dedicated download-url surface where supported by the endpoint.

5. What to monitor

  • job lifecycle status
  • webhook delivery success and retry counts
  • last delivery HTTP status
  • failure timestamps and error messages
These signals should live together in your internal async-job dashboard so operations can distinguish generation failures from webhook-delivery failures.
Last modified on June 11, 2026