Webhooks
Receive job results automatically when they complete, without polling.
Overview
Some AI models (image editing, video generation, etc.) can take longer than a typical HTTP request timeout. Instead of polling for the result, you can provide a webhook_url in your request. When the job completes, we POST the result directly to your URL.
This is the recommended approach for any model that may take more than a few seconds to generate.
How It Works
Include webhook_url in your POST /v1/models/{model}/run request body.
The API returns immediately with a job_id and status: "processing" (HTTP 202).
The model generates the result in the background.
When complete, we POST the result to your webhook_url.
Sending a Request with Webhook
curl -X POST https://api.abstrakt.one/v1/models/nano-banana-pro-edit/run \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": {
"prompt": "Make the background a beach scene",
"image_url": "https://example.com/photo.jpg"
},
"webhook_url": "https://your-server.com/api/abstrakt-webhook"
}'Initial Response (202)
{
"job_id": "job_abc123xyz",
"request_id": "fal_req_456",
"status": "processing",
"model_id": "nano-banana-pro-edit",
"message": "Model is still generating. Poll GET /v1/jobs/job_abc123xyz for the completed result."
}Webhook Payload
When the job finishes, we send a POST request to your webhook URL with the result:
Success Payload
{
"job_id": "job_abc123xyz",
"status": "succeeded",
"output": {
"type": "image",
"items": [
{
"type": "image",
"url": "https://cdn.abstrakt.one/outputs/abc123.png"
}
]
},
"credits_used": 1,
"completed_at": "2026-02-08T10:30:45Z"
}Failure Payload
{
"job_id": "job_abc123xyz",
"status": "failed",
"error": {
"code": "PROVIDER_FAILURE",
"message": "Model execution failed: invalid input image"
}
}Example Webhook Handler
Node.js / Express
app.post('/api/abstrakt-webhook', express.json(), (req, res) => {
const { job_id, status, output, error } = req.body;
if (status === 'succeeded') {
console.log(`Job ${job_id} completed!`);
// Process the output - e.g., save the image URL
const imageUrl = output?.items?.[0]?.url;
if (imageUrl) {
// Save to your database, notify your user, etc.
saveResult(job_id, imageUrl);
}
} else if (status === 'failed') {
console.error(`Job ${job_id} failed: ${error?.message}`);
}
// Return 200 quickly to acknowledge receipt
res.status(200).json({ received: true });
});Python / Flask
@app.route('/api/abstrakt-webhook', methods=['POST'])
def abstrakt_webhook():
data = request.json
job_id = data.get('job_id')
status = data.get('status')
if status == 'succeeded':
output = data.get('output', {})
items = output.get('items', [])
if items:
image_url = items[0].get('url')
# Save to your database, notify your user, etc.
save_result(job_id, image_url)
elif status == 'failed':
error = data.get('error', {})
print(f"Job {job_id} failed: {error.get('message')}")
return jsonify({"received": True}), 200Best Practices
- 1.Return quickly: Respond with a 2xx status within 10 seconds. Do any heavy processing asynchronously.
- 2.Be idempotent: Use the
job_idto check if you've already processed a result. Duplicate deliveries are possible. - 3.Use HTTPS: Webhook URLs should use HTTPS in production.
- 4.Have a fallback: You can always poll
GET /v1/jobs/{id}as a backup if you miss a webhook delivery.
Polling vs. Webhooks
| Approach | Best For | Pros | Cons |
|---|---|---|---|
| Polling | Simple integrations, scripts | No server needed, simple to implement | Wastes API calls, slight delay |
| Webhooks | Production apps, slow models | Instant delivery, no wasted calls | Requires a publicly accessible endpoint |
Tip: For the most robust integration, use both approaches. Set a webhook_url for instant delivery, and fall back to polling GET /v1/jobs/{id} if the webhook doesn't arrive within a reasonable time.