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.
01example.shBASH
01curl -X POST "https://queue.fal.run/fal-ai/ernie-image?fal_webhook=https://your.app/api/fal/webhook" \02 -H "Authorization: Key $FAL_KEY" \03 -H "Content-Type: application/json" \04 -d '{"prompt":"A vertical coffee shop poster. Large centered headline reads 'MORNING POUR' i...","aspect_ratio":"1:1","num_images":1,"enable_prompt_enhancer":true,"num_inference_steps":50,"seed":0}'
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.
01example.jsonJSON
01{02 "request_id": "019d9e0b-6eb0-7920-827a-868d042ec76e",03 "gateway_request_id": "01jav3...zy5",04 "status": "OK",05 "payload": {06 "images": [ { "url": "https://v3b.fal.media/files/..." } ],07 "seed": 422108 }09}
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.
01example.tsTS
01import express from "express";0203const app = express();04app.use(express.json());0506app.post("/api/fal/webhook", async (req, res) => {07 // Dedupe by request_id: fal may deliver the same webhook twice on retries.08 const { request_id, status, payload, error } = req.body;09 if (!request_id) return res.status(400).end();1011 // 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 });1718 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.