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

# Webhooks

> Receive real-time notifications for call events and actions.

# Webhooks

Webhooks let your application receive real-time notifications about events in Movoice AI. When an event occurs, we send an HTTP POST to the URL you specify.

## Setting Up Webhooks

1. Navigate to **Settings → Webhooks** in the Movoice Dashboard.
2. Enter your **Webhook URL** (must be an `https` endpoint).
3. Select which events to subscribe to.
4. Copy your **Webhook Secret** for signature verification.

<Tip>
  Use [Webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) during development to inspect incoming payloads before wiring up your own server.
</Tip>

***

## Event Types

### `call.started`

Triggered immediately when a call connects.

```json theme={null}
{
  "event_type": "call.started",
  "call_id": "call_998877",
  "agent_id": "agent_abc123",
  "direction": "outbound",
  "recipient_phone": "+919876543210",
  "timestamp": 1713456000
}
```

### `call.completed`

Triggered when a call ends. Includes AI summary, sentiment, and transcript URL.

```json theme={null}
{
  "event_type": "call.completed",
  "call_id": "call_998877",
  "duration": 45,
  "summary": "Customer booked a dental appointment for Friday at 2 PM.",
  "sentiment": "positive",
  "outcome": "BOOKED",
  "transcript_url": "https://storage.movoice.ai/transcripts/call_998877.json",
  "recording_url": "https://storage.movoice.ai/recordings/call_998877.mp3"
}
```

### `call.failed`

Triggered when a call could not be connected.

```json theme={null}
{
  "event_type": "call.failed",
  "call_id": "call_998878",
  "reason": "no_answer",
  "recipient_phone": "+919876543211",
  "timestamp": 1713456100
}
```

Possible `reason` values: `no_answer`, `busy`, `invalid_number`, `agent_error`, `network_error`.

### `action.triggered`

Triggered when an agent executes a tool (e.g., "Book Appointment", "Transfer Call").

```json theme={null}
{
  "event_type": "action.triggered",
  "call_id": "call_998877",
  "action_name": "book_appointment",
  "parameters": {
    "date": "2026-04-25",
    "time": "14:00",
    "patient_name": "Rahul Sharma"
  }
}
```

### `transfer.initiated`

Triggered when an agent initiates a warm transfer to a human agent.

```json theme={null}
{
  "event_type": "transfer.initiated",
  "call_id": "call_998877",
  "transfer_to": "+919000000001",
  "reason": "Customer requested human support"
}
```

***

## Security & Verification

Every webhook request includes an `X-Movoice-Signature` header. Always verify this before processing:

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

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

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

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

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

  # Flask example
  @app.route('/webhook', methods=['POST'])
  def webhook():
      sig = request.headers.get('X-Movoice-Signature')
      if not verify_signature(request.data, sig, WEBHOOK_SECRET):
          return 'Invalid signature', 401
      event = request.json
      # handle event
      return '', 200
  ```
</CodeGroup>

***

## Retries

If your server returns a non-2xx response, Movoice retries the webhook with exponential backoff:

| Attempt   | Delay      |
| :-------- | :--------- |
| 1st retry | 30 seconds |
| 2nd retry | 5 minutes  |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours    |

After 4 failed attempts, the event is dropped and logged in your dashboard under **Settings → Webhooks → Failed Events**.
