Check out the newest way to compare different models for a task/agent harness: AutoEvals
Async API

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 notifications within milliseconds of generation completion
  • 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:

  1. Accept JSON payloads
  2. Respond with HTTP 200 status immediately
  3. Process the webhook data asynchronously
TypeScript
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

  1. Navigate to the inference.net dashboard
  2. Go to SettingsIntegrations in the sidebar
  3. Click Create Webhook
  4. Enter a descriptive name and your HTTPS endpoint URL
  5. Save your webhook

You'll receive a webhook identifier (e.g., AhALzdz8S) that you'll use when creating generations.

Include the webhook identifier in the metadata when creating a generation:

TypeScript
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.

Webhook Events

generation.completed

Sent when a generation finishes processing (successfully or with failure):

JSON
{
  "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.

TypeScript
import type { OpenAI } from "openai";

const response = responseJsonObject as OpenAI.Chat.Completions.ChatCompletion;

async-embedding.completed

Sent when an async embedding request finishes processing:

JSON
{
  "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:

HeaderDescriptionExample
X-Inference-EventEvent typegeneration.completed
X-Inference-Webhook-IDWebhook identifierAhALzdz8S
X-Inference-Generation-IDGeneration ID (for generation events)XBKcs7F1s2oJ_AHiLMbF4
X-Inference-Embedding-IDGeneration ID (for embedding events)EMB_abc123
User-Agentinference.net webhook agentKuzco-Webhook/1.0
Content-TypeAlways application/jsonapplication/json

Before each delivery, the service also sends an OPTIONS preflight request to your endpoint with the same X-Inference-* headers. The preflight must return a 2xx status or the delivery is retried.

(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:

  1. Navigate to SettingsIntegrations in the dashboard
  2. Find your webhook in the list
  3. Click the menu and select Test
  4. Check your endpoint logs for the test payload

A successful test will show a green success indicator in the dashboard.

Best Practices

1. Respond Immediately

Your endpoint must respond within 60 seconds. Always return a 200 status immediately and process the webhook asynchronously:

TypeScript
// 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 may be retried. Use data.id to ensure you don't process the same event twice:

TypeScript
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:

TypeScript
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:

TypeScript
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

  1. Check webhook status: Ensure your webhook is not disabled in the dashboard
  2. Test connectivity: Use the test feature in the dashboard
  3. Verify URL: Confirm your endpoint is publicly accessible via HTTPS
  4. Check logs: Review both your server logs and any reverse proxy logs
  5. Validate metadata: Ensure you're including the correct webhook_id in 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 (or groupId for 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: Failed webhook deliveries are retried up to 1200 times at fixed intervals — 30 seconds for generation events and 5 minutes for group events. After all attempts are exhausted, the delivery is marked as failed.

Q: What's the webhook timeout? A: Webhook endpoints must respond within 60 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: All webhooks are sent over 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:

On this page