---
title: "Webhooks"
url: https://docs.aila.fyi/api-reference/webhooks
source: https://docs.aila.fyi/llms.txt
---

# Webhooks

Aila sends signed HTTPS `POST` requests to your endpoint when calls finish processing, data is extracted, tasks and appointments are created, or a MISMO 3.4 file is ready.

---

## Events

| Category | Event | Sent when |
| :--- | :--- | :--- |
| **Calls** | `call.completed` | A call finishes processing. The payload is lightweight (IDs, time, duration, contact); fetch details with the [API](https://docs.aila.fyi/api-reference). |
| **Calls** | `call.data.extracted` | Structured data from your [Call setup](https://docs.aila.fyi/settings/call-setup) is ready. |
| **Tasks** | `task.created` | Aila creates a follow-up task from a call. |
| **Tasks** | `task.synced` | A task syncs to your CRM. |
| **Appointments** | `appointment.created` | Aila creates an appointment from a call. |
| **Appointments** | `appointment.synced` | An appointment syncs to your CRM calendar. |
| **Loan files** | `mismo.ready` | A MISMO 3.4 file is generated for a call or contact. |
| **Testing** | `test.ping` | Only when you click **Send test event**. |

`call.completed` and `call.data.extracted` are sent once per call.

---

## Add an endpoint

### In the app

1. Go to **Settings > Connections** and open **Outgoing webhooks**.
2. Click **New webhook**.
3. Enter a **Name** and the **URL**. The URL must be HTTPS and publicly reachable (no localhost or private IPs).
4. Select the events to send.
5. Save, then copy the **signing secret**. It's shown only once.
6. Click **Send test event** to check your endpoint.

Each endpoint's **Recent deliveries** shows the event, status, number of attempts, and time.

### With the API
```bash
curl -X POST https://app.aila.fyi/api/v1/organizations/{organizationId}/webhooks \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Internal CRM ingest",
    "url": "https://api.yourdomain.com/webhooks/aila",
    "eventTypes": ["call.completed", "call.data.extracted"]
  }'
```
The response includes the signing secret. The API also supports listing, updating, deleting, sending a test event, rotating the secret, and listing deliveries. See the [OpenAPI spec](https://app.aila.fyi/api/openapi).

---

## Payload
```json
{
  "id": "evt_...",
  "type": "call.completed",
  "createdAt": "2026-09-30T14:30:00.000Z",
  "organizationId": "org_...",
  "data": {
    "callId": "...",
    "organizationId": "org_...",
    "recordedAt": "2026-09-30T14:02:11.000Z",
    "durationSeconds": 642,
    "contactId": "...",
    "contactName": "Dana Brooks",
    "contactPhone": "+19195550123"
  }
}
```
### Headers

| Header | Value |
| :--- | :--- |
| `X-Aila-Event` | The event type, for example `call.completed`. |
| `X-Aila-Delivery-Id` | Unique per delivery. Use it to ignore duplicates. |
| `X-Aila-Webhook-Endpoint-Id` | The endpoint that received it. |
| `X-Aila-Signature` | `t= ,v1= ` |

---

## Verify the signature

The signature is an HMAC-SHA256 of ` . ` using your signing secret. Reject requests older than 5 minutes.
```javascript
import crypto from 'crypto'

function verifyAilaWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((kv) => kv.split('=')))
  const timestamp = Number(parts.t)
  if (!timestamp || !parts.v1) return false
  if (Math.abs(Date.now() - timestamp) / 1000 > toleranceSeconds) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex')
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(parts.v1, 'hex')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}
```
Use the raw request body exactly as received, before parsing JSON.

To rotate the secret, click **Rotate signing secret** on the endpoint. The old secret stops working immediately.

---

## Delivery and retries

- **Success:** any `2xx` response within **15 seconds**. Redirects count as failures.
- **Retries:** up to **5 attempts in total**, with exponential backoff starting at 30 seconds (up to 1 hour between attempts, 7 hours overall).
- **Auto-disable:** an endpoint is disabled after **20 consecutive failed deliveries**. Fix the endpoint, then re-enable it in settings.
