API
Getting Started With Webhooks
Everything you need to know to get started with webhooks.
Webhook support is available for all slow endpoints: /chat/completions, /completions, and /embeddings calls.
Overview
Webhooks provide an efficient push-based notification system for tracking generation completions in real-time. Rather than repeatedly polling the API to check generation status, webhooks automatically notify your application when generations complete, enabling streamlined workflows and better resource utilization.
Key Benefits
- Resource Efficiency: Eliminate unnecessary API calls for status checks
- Real-time Updates: Receive a notification about one second after a generation completes
- Scalability: Handle thousands of concurrent generations efficiently
- Improved User Experience: Update your UI instantly when results are ready
Getting Started
Step 1: Create a Webhook Endpoint
Your application needs an HTTPS endpoint capable of receiving POST requests. The endpoint should:
- Accept JSON payloads
- Respond with HTTP 200 status 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");
});Go Example
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type WebhookPayload struct {
Event string `json:"event"`
Timestamp string `json:"timestamp"`
WebhookID string `json:"webhookId"`
Data map[string]any `json:"data"`
}
func handleWebhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
var payload WebhookPayload
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Verify webhook source
webhookID := r.Header.Get("X-Inference-Webhook-ID")
// Process asynchronously
go func() {
switch payload.Event {
case "generation.completed":
fmt.Printf("Processing generation %s\n", payload.Data["id"])
// Your processing logic here
case "async-embedding.completed":
fmt.Printf("Processing embedding %s\n", payload.Data["id"])
// Your embedding processing logic here
}
}()
// Respond immediately
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func main() {
http.HandleFunc("/webhooks/inference", handleWebhook)
fmt.Println("Webhook server listening on :3000")
http.ListenAndServe(":3000", nil)
}Step 2: Deploy Your Endpoint
Your webhook endpoint must be publicly accessible via HTTPS. For development environments, consider using:
- ngrok:
ngrok http 3000 - Cloudflare Tunnel: Provides a stable URL
- localtunnel:
lt --port 3000
Step 3: Register Your Webhook
- Navigate to the inference.net dashboard
- Go to Settings → Integrations in the sidebar
- Click Create Webhook
- Enter a descriptive name and your HTTPS endpoint URL
- Save your 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'll receive a webhook identifier (e.g., AhALzdz8S) that you'll use when creating generations.
Step 4: Link Webhook to Generations
Include the webhook identifier in the metadata when creating 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: "gemma-3-27b-it",
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 webhook endpoint will receive a notification.
If webhook_id does not match a webhook in your team, the request fails with 400 and error type WebhookNotFound. A disabled webhook is accepted, but no notification is sent.
Webhook Events
generation.completed
Sent when a generation finishes processing (successfully or with 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": "gemma-3-27b-it",
"stream": false,
"max_tokens": 100,
"metadata": {
"webhook_id": "AhALzdz8S"
}
},
"response": {
"id": "XBKcs7F1s2oJ_AHiLMbF4",
"object": "chat.completion",
"created": 1748933182,
"model": "gemma-3-27b-it",
"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": "gemma-3-27b-it",
"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:
{
"event": "async-embedding.completed",
"timestamp": "2025-01-15T10:30:00Z",
"webhookId": "AhALzdz8S",
"data": {
"id": "EMB_abc123",
"state": "Success",
"stateMessage": "Embeddings generated successfully",
"request": {
"model": "qwen/qwen3-embedding-4b",
"input": ["text1", "text2"],
"metadata": { "webhook_id": "AhALzdz8S" }
},
"response": {
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [0.0023064255, -0.009327292]
}
],
"model": "qwen/qwen3-embedding-4b",
"usage": {
"prompt_tokens": 100,
"total_tokens": 100
}
},
"model": "qwen/qwen3-embedding-4b",
"createdAt": "2025-01-15T10:29:55Z",
"dispatchedAt": "2025-01-15T10:29:56Z",
"firstChunkAt": "2025-01-15T10:29:58Z",
"finishedAt": "2025-01-15T10:30:00Z",
"usage": {
"prompt_tokens": 100,
"total_tokens": 100
}
}
}The generation ID is the data.id field. The response object follows the standard OpenAI embeddings format.
Headers
All webhook requests include the following 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.
(Security) Verifying the request source
The X-Inference-Webhook-ID is a good way to verify that the payload you're receiving is officially coming from our API.
This ID is unique to your webhook, and is completely private to you and your team.
If the ID does not match what you see in the dashboard, your endpoint has most likely been discovered by a malicious actor.
Testing Webhooks
You can test your webhook endpoint from the dashboard:
- Navigate to Settings → Integrations in the dashboard
- Find your webhook in the list
- Click the menu and select Test
- Check your endpoint logs for the test payload
The test sends one POST with sample data. Test ids start with test-, so your endpoint can tell a test from a real event.
A successful test will show a green success indicator in the dashboard.
Best Practices
1. 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");
});2. Implement Idempotency
Failed webhooks are retried, and a retry can arrive after your endpoint already processed the event. Use data.id to ensure 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.
3. Validate Webhook Source
Always verify that webhooks originate from inference.net by checking the presence of expected headers:
function validateWebhookSource(headers: Record<string, string>): boolean {
const requiredHeaders = ["x-inference-webhook-id", "x-inference-event"];
return requiredHeaders.every((header) => headers[header]);
}4. Handle Errors Gracefully
Implement proper error handling to prevent individual failures from affecting your entire 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
}
}5. Monitor Webhook Processing
Track key metrics to ensure reliable webhook processing:
- Webhook receipt rate
- Processing success/failure rates
- Average processing time
- Queue depth (if using queues)
Troubleshooting
Not Receiving Webhooks
- Check webhook status: Ensure your webhook is not disabled in the dashboard
- Test connectivity: Use the test feature in the dashboard
- Verify URL: Confirm your endpoint is publicly accessible via HTTPS
- Check logs: Review both your server logs and any reverse proxy logs
- Validate metadata: Ensure you're including the correct
webhook_idin generation requests
Webhooks Arriving Late
- Verify your endpoint responds quickly (< 1 second ideally)
- Check that you're not performing heavy processing before responding
- Monitor your server load and resource usage
Duplicate Webhook Deliveries
- Implement idempotency using
data.id(orgroupIdfor group events) - Ensure your endpoint always returns 200 OK for successful receipt
- Check for any errors in your webhook processing that might trigger retries
Frequently Asked Questions
Q: What happens if my endpoint is down? A: 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.
Q: What's the webhook timeout? A: Webhook endpoints must respond within 30 seconds. A timeout is treated as a failure and will trigger retries.
Q: Can I filter which events I receive? A: Currently, webhooks receive all event types. Event filtering is planned for a future update.
Q: How secure are webhooks? A: Webhook URLs must use HTTPS. You should validate the webhook source using the provided headers. HMAC signature verification is planned for additional security.
Q: What's the maximum payload size? A: 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.
Q: Can I replay missed webhooks? A: Webhook replay functionality is not currently available. As a fallback, you can poll the generation status endpoint.
Support
For assistance with webhooks:
- Email: support@inference.net
- Discord: Join our developer community
- Documentation: https://docs.inference.net
- Issues: Report bugs via our support portal