> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modelroute.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Receiving Results via Webhooks

> The recommended way to receive execution results at scale. Instant delivery, zero wasted API calls.

## Why webhooks for production

Webhooks are the **recommended way** to receive execution results in production. Instead of polling (which wastes API calls and is rate-limited), ModelRoute pushes results to your endpoint the moment they're ready.

|             | Polling                                       | Webhooks                                               |
| ----------- | --------------------------------------------- | ------------------------------------------------------ |
| Latency     | Depends on poll interval                      | Instant (\< 1 second)                                  |
| API calls   | Many (most return "still processing")         | One (the result)                                       |
| Rate limits | Constrained                                   | Not applicable                                         |
| Scalability | Poor — each execution needs its own poll loop | Unlimited — handles thousands of concurrent executions |
| Complexity  | Simple loop                                   | Endpoint + signature verification                      |

<Tip>
  **Start with polling, graduate to webhooks.** Polling is perfect when you're building your first integration and want to see results quickly. Once you're ready for production, set up a webhook endpoint — it takes 10 minutes and gives you a dramatically better experience at scale.
</Tip>

## Setup

### 1. Register a webhook endpoint

```bash theme={null}
curl -X POST https://api.modelroute.ai/v1/webhooks/endpoints \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/modelroute",
    "events": ["execution.completed", "execution.failed"],
    "description": "Production webhook"
  }'
```

```json theme={null}
{
  "id": "wh_abc123",
  "url": "https://your-app.com/webhooks/modelroute",
  "secret": "whsec_a1b2c3d4e5f6...",
  "events": ["execution.completed", "execution.failed"],
  "status": "active"
}
```

<Warning>
  Save the `secret` immediately — it's only shown once. You'll need it to verify webhook signatures.
</Warning>

### 2. Create executions as normal

No changes to your execution requests. ModelRoute automatically delivers webhook events to all active endpoints subscribed to the relevant events.

### 3. Receive events

When an execution completes, ModelRoute sends a `POST` to your endpoint:

```json theme={null}
{
  "id": "evt_abc123",
  "type": "execution.completed",
  "timestamp": "2026-03-20T10:30:08Z",
  "data": {
    "id": "exec_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "COMPLETED",
    "model": "flux-1.1-pro",
    "cost": "0.040",
    "latency_ms": 8420,
    "result_url": "https://api.modelroute.ai/v1/executions/exec_a1b2.../result",
    "created_at": "2026-03-20T10:30:00Z",
    "completed_at": "2026-03-20T10:30:08Z"
  }
}
```

### 4. Verify the signature

Always verify the HMAC-SHA256 signature before processing. See [webhook verification](/webhooks/verification) for code samples in Python, TypeScript, Go, and bash.

### 5. Respond with 200

Return a `200 OK` to acknowledge receipt. If your endpoint returns a non-2xx status, ModelRoute retries with exponential backoff (up to 5 attempts over 24 hours).

## Webhook events

| Event                 | When                             | What to do                             |
| --------------------- | -------------------------------- | -------------------------------------- |
| `execution.completed` | AI model finished successfully   | Fetch result, deliver to your user     |
| `execution.failed`    | AI model failed                  | Check `error.code`, retry if retryable |
| `execution.started`   | Execution dispatched to provider | Update UI status (optional)            |
| `execution.cancelled` | Execution cancelled              | Update UI status (optional)            |

## Example: end-to-end flow

<CodeGroup>
  ```python Python (Flask) theme={null}
  from flask import Flask, request, jsonify
  import hmac
  import hashlib
  import requests

  app = Flask(__name__)
  WEBHOOK_SECRET = "whsec_your_secret_here"
  API_KEY = "sk_your_api_key_here"

  @app.route("/webhooks/modelroute", methods=["POST"])
  def handle_webhook():
      # 1. Verify signature
      signature = request.headers.get("X-Signature")
      timestamp = request.headers.get("X-Signature-Timestamp")
      payload = f"{timestamp}.{request.get_data(as_text=True)}"
      expected = hmac.new(
          WEBHOOK_SECRET.encode(), payload.encode(), hashlib.sha256
      ).hexdigest()

      if not hmac.compare_digest(signature, expected):
          return "Invalid signature", 401

      # 2. Process the event
      event = request.json
      if event["type"] == "execution.completed":
          execution_id = event["data"]["id"]

          # 3. Fetch the full result
          result = requests.get(
              f"https://api.modelroute.ai/v1/executions/{execution_id}/result",
              headers={"Authorization": f"Bearer {API_KEY}"}
          ).json()

          # 4. Do something with the result
          print(f"Execution {execution_id} completed: {result}")

      return jsonify({"received": True}), 200
  ```

  ```typescript TypeScript (Express) theme={null}
  import express from "express";
  import crypto from "crypto";

  const app = express();
  app.use(express.raw({ type: "application/json" }));

  const WEBHOOK_SECRET = "whsec_your_secret_here";

  app.post("/webhooks/modelroute", (req, res) => {
    const signature = req.headers["x-signature"] as string;
    const timestamp = req.headers["x-signature-timestamp"] as string;
    const payload = `${timestamp}.${req.body.toString()}`;

    const expected = crypto
      .createHmac("sha256", WEBHOOK_SECRET)
      .update(payload)
      .digest("hex");

    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(req.body.toString());

    if (event.type === "execution.completed") {
      console.log(`Execution ${event.data.id} completed`);
      // Fetch result and process...
    }

    res.json({ received: true });
  });

  app.listen(3000);
  ```
</CodeGroup>

## Retry policy

If your endpoint is unreachable or returns a non-2xx status:

| Attempt   | Delay               |
| --------- | ------------------- |
| 1st retry | 1 minute            |
| 2nd retry | 4 minutes           |
| 3rd retry | 16 minutes          |
| 4th retry | 30 minutes (capped) |
| 5th retry | 30 minutes          |

After 5 failed attempts (or 24 hours), the delivery is marked as failed. You can manually retry failed deliveries via the API.
