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

# Webhooks

> Receive real-time event notifications from Avero via HTTP POST.

## Overview

Avero sends a `POST` request to your endpoint URL each time a subscribed event occurs. The request body is a JSON object; the `event` field identifies the type.

## Registering a webhook

1. Go to **Settings → Developers → Webhooks** in the Avero desktop app.
2. Click **Add endpoint**, enter your URL, and select the events you want.
3. Save — Avero stores the endpoint and generates a signing secret.

## Events

| Event | Fired when |
| - | - |
| `call.ended` | A call attempt ends (any outcome) |
| `call.outcome_set` | An agent saves a call outcome |
| `recording.ready` | The call recording is available in Storage |
| `transcript.ready` | AI transcription of the recording is complete |
| `lead.dnc` | A lead is marked Do Not Call |

### Payload shape

```json theme={null}
{
  "id": "evt_01j8...",
  "event": "call.ended",
  "created_at": "2026-10-09T12:34:56Z",
  "workspace_id": "ws_...",
  "data": { ... }
}
```

The `data` object is event-specific. For `call.ended` it includes `call_id`, `phone_e164`, `duration_seconds`, and `technical_result`.

## Verifying the signature

Every delivery includes an `X-Payfair-Signature` header containing an HMAC-SHA256 digest of the raw request body, prefixed with `sha256=`.

**Always verify the signature before processing the payload.** This prevents spoofed requests.

```
X-Payfair-Signature: sha256=a4c9f3...
```

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require("crypto");

  function verifySignature(secret, rawBody, signatureHeader) {
    const expected =
      "sha256=" +
      crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signatureHeader)
    );
  }

  // Express example
  app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
    const sig = req.headers["x-payfair-signature"];
    if (!verifySignature(process.env.WEBHOOK_SECRET, req.body, sig)) {
      return res.status(401).send("Invalid signature");
    }
    const event = JSON.parse(req.body);
    console.log("Received event:", event.event);
    res.status(200).send("ok");
  });
  ```

  ```python Python theme={null}
  import hashlib
  import hmac
  import os

  def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
      expected = "sha256=" + hmac.new(
          secret.encode(), raw_body, hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(expected, signature_header)

  # FastAPI example
  from fastapi import FastAPI, Request, HTTPException

  app = FastAPI()

  @app.post("/webhook")
  async def handle_webhook(request: Request):
      raw_body = await request.body()
      sig = request.headers.get("x-payfair-signature", "")
      if not verify_signature(os.environ["WEBHOOK_SECRET"], raw_body, sig):
          raise HTTPException(status_code=401, detail="Invalid signature")
      event = await request.json()
      print("Received event:", event["event"])
      return {"ok": True}
  ```
</CodeGroup>

<Warning>
  Use the **raw request body** (before any JSON parsing) for HMAC computation. Parsing and re-serialising the JSON can change byte order and invalidate the signature.
</Warning>

## Retry schedule

If your endpoint returns a non-`2xx` status or times out, Avero retries the delivery on this schedule:

| Attempt | Delay after previous attempt |
| - | - |
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 24 hours |

After 5 failed retries the delivery is marked **failed**. You can manually replay individual deliveries from **Settings → Developers → Webhook delivery log**.

## Best practices

* **Respond quickly.** Return `200 OK` as soon as you receive the request, then process asynchronously. Avero times out after 10 seconds.
* **Make handlers idempotent.** The same event can be delivered more than once; use `id` to deduplicate.
* **Check `event` before acting.** Subscribe only to the events you need; ignore unknown events gracefully.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.