Webhooks
Get a push notification when an asynchronous generation or group finishes instead of polling for its status.
Webhooks notify your application when an asynchronous generation completes.
Instead of repeatedly polling the API for the status of a
/v1/slow or group
request, your endpoint receives a POST when the result is ready.
Why use webhooks
- Resource efficiency: no API calls spent on status checks
- Real-time updates: a notification arrives about one second after a generation completes
- Scalability: handle thousands of concurrent generations
- Better user experience: update your UI as soon as results are ready
Set up a webhook
Create a webhook endpoint
Your application needs an HTTPS endpoint that accepts POST requests. The endpoint should:
- Accept JSON payloads
- Respond with HTTP 200 immediately
- Process the webhook data asynchronously
import express from "express"; const app = express(); app.post("/webhooks/inference", express.json(), (req, res) => { const { event, data } = req.body; // Verify webhook source via headers const webhookId = req.headers["x-inference-webhook-id"]; if (event === "generation.completed") { console.log(`Generation ${data.id} completed with status: ${data.state}`); // Process asynchronously setImmediate(() => { processGenerationResult(data); }); } else if (event === "async-embedding.completed") { console.log(`Embedding ${data.id} completed with status: ${data.state}`); setImmediate(() => { processEmbeddingResult(data); }); } // Always respond immediately res.status(200).json({ received: true }); }); app.listen(3000, () => { console.log("Webhook receiver listening on port 3000"); });Deploy your endpoint
Your webhook endpoint must be publicly reachable over HTTPS. For development, consider:
- ngrok:
ngrok http 3000 - Cloudflare Tunnel: provides a stable URL
- localtunnel:
lt --port 3000
- ngrok:
Register the webhook
- Go to Settings → Webhooks in the dashboard.
- Create a webhook.
- Enter a descriptive name and your HTTPS endpoint URL.
- Save the webhook.
The URL must use HTTPS and point to a public host. URLs for
localhost, private networks, or IP addresses in private ranges are rejected.You get a webhook identifier (for example
AhALzdz8S) to use when you create generations.Link the webhook to generations
Include the webhook identifier in the request metadata when you create a generation:
import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.inference.net/v1/slow", apiKey: process.env.INFERENCE_API_KEY, }); const response = await client.chat.completions.create({ model: "kimi-k3-fast", messages: [{ role: "user", content: "Explain quantum computing" }], // @ts-expect-error metadata is not in the OpenAI SDK types metadata: { webhook_id: "AhALzdz8S", }, });When the generation completes, your endpoint receives a notification.
If
webhook_iddoes not match a webhook in your team, the request fails with400and error typeWebhookNotFound. A disabled webhook is accepted, but no notification is sent.
Webhook events
generation.completed
Sent when a generation finishes processing, successfully or with a failure:
{
"event": "generation.completed",
"timestamp": "2025-01-03T06:46:22.838Z",
"webhookId": "AhALzdz8S",
"data": {
"id": "XBKcs7F1s2oJ_AHiLMbF4",
"state": "Success",
"stateMessage": "Generation successful",
"request": {
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "Explain quantum computing" }
],
"model": "kimi-k3-fast",
"stream": false,
"max_tokens": 100,
"metadata": {
"webhook_id": "AhALzdz8S"
}
},
"response": {
"id": "XBKcs7F1s2oJ_AHiLMbF4",
"object": "chat.completion",
"created": 1748933182,
"model": "kimi-k3-fast",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Quantum computing is a revolutionary approach..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 28,
"completion_tokens": 42,
"total_tokens": 70
}
},
"model": "kimi-k3-fast",
"createdAt": "2025-01-03T06:46:18.000Z",
"dispatchedAt": "2025-01-03T06:46:19.100Z",
"firstChunkAt": "2025-01-03T06:46:20.200Z",
"finishedAt": "2025-01-03T06:46:22.307Z",
"usage": {
"prompt_tokens": 28,
"completion_tokens": 42,
"total_tokens": 70
}
}
}The generation ID is the data.id field. The response object is compatible
with the types exported from the official OpenAI SDKs.
import type { OpenAI } from "openai";
const response = responseJsonObject as OpenAI.Chat.Completions.ChatCompletion;async-embedding.completed
Sent when an async embedding request finishes processing. The payload has the
same shape as generation.completed, with an embeddings request in request
and a standard OpenAI embeddings response in response. The generation ID is
the data.id field. See the
webhooks reference
for the full structure.
slow-group.completed
Sent when every request in a group finishes, or when the group fails. See the webhooks reference for the payload.
Headers
All webhook requests include these headers:
| Header | Description | Example |
|---|---|---|
X-Inference-Event | Event type | generation.completed |
X-Inference-Webhook-ID | Webhook identifier | AhALzdz8S |
X-Inference-Generation-ID | Generation ID (for generation events) | XBKcs7F1s2oJ_AHiLMbF4 |
X-Inference-Embedding-ID | Generation ID (for embedding events) | EMB_abc123 |
X-Inference-Group-ID | Group ID (for group events) | GRP_XYZ123 |
X-Inference-Delivery-Attempt | Delivery attempt, starting at 1 | 1 |
User-Agent | Inference.net webhook agent | Kuzco-Webhook/1.0 |
Content-Type | Always application/json | application/json |
Embedding events send X-Inference-Generation-ID too, with the same value as
X-Inference-Embedding-ID. Each delivery attempt is a single POST request.
There is no OPTIONS preflight.
Verify the request source
Test a webhook
Test your endpoint from the dashboard:
- Go to Settings → Webhooks in the dashboard.
- Find your webhook in the list.
- Click Test, choose an event type, and click Send test.
- Check your endpoint logs for the test payload.
The test sends one POST with a sample payload for the event type you chose, in
the same shape as a real delivery. Test IDs start with test-, so your endpoint
can tell a test from a real event. See
dashboard test events.
Manage webhooks
Settings → Webhooks lists your active and disabled webhooks. From there you can:
- Create a webhook with a name and URL
- Send a test event
- Disable a webhook to stop notifications, and enable it again
- Delete a webhook
- See recent invocation stats
Best practices
Respond immediately
Your endpoint must respond within 30 seconds. Always return a 200 status immediately and process the webhook asynchronously:
// Correct approach
app.post("/webhook", (req, res) => {
res.status(200).send("OK");
processWebhookAsync(req.body);
});
// Incorrect approach — may timeout
app.post("/webhook", async (req, res) => {
await heavyProcessing(req.body); // Risk of timeout
res.status(200).send("OK");
});Make processing idempotent
Failed webhooks are retried, and a retry can arrive after your endpoint already
processed the event. Use data.id to make sure you don't process the same event
twice. The timestamp field is the same on every attempt for an event, and
X-Inference-Delivery-Attempt tells you which attempt you received:
const processedGenerations = new Set<string>();
async function processWebhook(payload: any) {
const generationId = payload.data.id;
if (processedGenerations.has(generationId)) {
return; // Already processed
}
processedGenerations.add(generationId);
// Process the generation
}For slow-group.completed events, use the top-level groupId field for
idempotency.
Validate the webhook source
Verify that webhooks come from Inference.net by checking for the expected headers:
function validateWebhookSource(headers: Record<string, string>): boolean {
const requiredHeaders = ["x-inference-webhook-id", "x-inference-event"];
return requiredHeaders.every((header) => headers[header]);
}Handle errors gracefully
Handle errors so one failure doesn't affect the rest of your system:
async function handleWebhook(payload: any) {
try {
await processWebhook(payload);
} catch (error) {
console.error("Webhook processing failed:", error);
// Log to monitoring service
// Return 200 to prevent unnecessary retries
}
}Monitor webhook processing
Track these metrics to keep webhook processing reliable:
- Webhook receipt rate
- Processing success and failure rates
- Average processing time
- Queue depth (if you use queues)
Troubleshooting
Not receiving webhooks
- Check webhook status: make sure the webhook is not disabled in Settings → Webhooks.
- Test connectivity: send a test event from the dashboard.
- Verify the URL: confirm your endpoint is publicly reachable over HTTPS.
- Check logs: review your server logs and any reverse proxy logs.
- Validate metadata: make sure you include the correct
webhook_idin generation requests.
Webhooks arriving late
- Make sure your endpoint responds quickly (ideally under 1 second).
- Don't do heavy processing before responding.
- Monitor your server load and resource usage.
Duplicate deliveries
- Make processing idempotent with
data.id(orgroupIdfor group events). - Make sure your endpoint always returns 200 OK for a successful receipt.
- Check for errors in your webhook processing that might trigger retries.
FAQ
What happens if my endpoint is down? A failed delivery is retried after 5 seconds, 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour, and 2 hours, and then every 4 hours. Each delay varies by up to 20%. After 24 attempts (about 3 days), the event is dropped. Use the generation status endpoint to get results for a dropped event.
What's the webhook timeout? Webhook endpoints must respond within 30 seconds. A timeout is treated as a failure and triggers retries.
Can I filter which events I receive? No. Webhooks receive all event types.
How secure are webhooks? Webhook URLs must use HTTPS. Validate the webhook source with the headers above. Webhooks are not signed with an HMAC signature.
What's the maximum payload size? Payloads scale with the size of the generation they describe; a payload is larger for generations with long requests or responses. There is no fixed size limit.
Can I replay missed webhooks? No. As a fallback, poll the generation status endpoint.
Support
For help with webhooks, email support@inference.net.