Reference

Webhooks

For anything that runs longer than a browser is willing to wait, register a webhook on submit and take the result whenever it lands. Cheaper than polling, kinder to users.

Registering a webhook

Append ?fal_webhook=<url> to any queue submit. fal posts the final result to the URL you provide. The URL must be reachable over HTTPS from the public internet.

BASH
1curl -X POST "https://queue.fal.run/fal-ai/happyhorse/v1/text-to-video?fal_webhook=https://your.app/api/fal/webhook" \
2 -H "Authorization: Key $FAL_KEY" \
3 -H "Content-Type: application/json" \
4 -d '{"prompt":"Documentary style interview at night. A barista in a rain jacket leans agains...","duration":"5s","resolution":"720p","aspect_ratio":"16:9","generate_audio":true,"language":"en"}'

Payload shape

The body is JSON with a stable top-level shape. The nested payload matches the model's own output schema, identical to what /docs/api-reference documents for synchronous calls.

JSON
1{
2 "request_id": "019d9e0b-6eb0-7920-827a-868d042ec76e",
3 "gateway_request_id": "01jav3...zy5",
4 "status": "OK",
5 "payload": {
6 "images": [ { "url": "https://v3b.fal.media/files/..." } ],
7 "seed": 4221
8 }
9}

Handler (Node / Express)

fal may deliver the same webhook more than once on retry. Always dedupe on the request_id. Respond with a 2xx as soon as your write is durable; long handlers risk a redelivery.

TS
1import express from "express";
2
3const app = express();
4app.use(express.json());
5
6app.post("/api/fal/webhook", async (req, res) => {
7 // Dedupe by request_id: fal may deliver the same webhook twice on retries.
8 const { request_id, status, payload, error } = req.body;
9 if (!request_id) return res.status(400).end();
10
11 // Idempotent write: upsert on request_id.
12 await db.generations.upsert({
13 where: { request_id },
14 update: { status, result: payload, error },
15 create: { request_id, status, result: payload, error },
16 });
17
18 res.status(200).end();
19});

Rules

  • Idempotent writes only. Upsert on request_id.
  • Accept body within 10 seconds; return 2xx immediately; process asynchronously if needed.
  • Treat the endpoint as public. Sign and verify if you handle sensitive payloads; rotate the signing secret periodically.
  • Fall back to polling (/docs/async-tasks) when you cannot expose a public URL.
Also reading