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/gpt-image-2?fal_webhook=https://your.app/api/fal/webhook" \2 -H "Authorization: Key $FAL_KEY" \3 -H "Content-Type: application/json" \4 -d '{"prompt":"A dense East-Asian bodega storefront at dusk, hand-painted signs with the exa...","safety_tolerance":2,"size":"1024x1024","quality":"medium","num_images":1}'
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": 42218 }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";23const app = express();4app.use(express.json());56app.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();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.