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

# Webhooks Overview

> Receive real-time notifications when events occur in Mavio. Set up webhook endpoints, verify signatures, and handle retries.

## How webhooks work

Instead of polling the API for changes, you can register a webhook endpoint to receive HTTP POST notifications in real time when events occur -- such as when a meeting completes, a transcript is ready, or an action item is created.

```
1. You register a webhook URL (e.g., https://yourapp.com/webhooks/mavio)
2. An event occurs in Mavio (e.g., a transcript finishes processing)
3. Mavio sends an HTTP POST to your URL with the event payload
4. Your server responds with 2xx to acknowledge receipt
```

## Setting up a webhook

### Create a webhook subscription

```bash theme={null}
curl -X POST https://api.mavioapp.com/v1/webhooks \
  -H "Authorization: Bearer mvo_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/mavio",
    "events": ["meeting.completed", "transcript.ready", "summary.ready"],
    "secret": "whsec_your_signing_secret"
  }'
```

```json Response theme={null}
{
  "id": "whk_a1b2c3d4e5",
  "url": "https://yourapp.com/webhooks/mavio",
  "events": ["meeting.completed", "transcript.ready", "summary.ready"],
  "status": "active",
  "created_at": "2026-04-10T12:00:00Z"
}
```

<Note>
  Your webhook URL must be publicly accessible and support HTTPS. HTTP endpoints are rejected in production.
</Note>

### Manage webhook subscriptions

| Action            | Method   | Endpoint           |
| ----------------- | -------- | ------------------ |
| List all webhooks | `GET`    | `/v1/webhooks`     |
| Get a webhook     | `GET`    | `/v1/webhooks/:id` |
| Create a webhook  | `POST`   | `/v1/webhooks`     |
| Update a webhook  | `PATCH`  | `/v1/webhooks/:id` |
| Delete a webhook  | `DELETE` | `/v1/webhooks/:id` |

## Webhook payload format

Every webhook delivery sends a JSON payload with the following structure:

```json theme={null}
{
  "id": "evt_f6g7h8i9j0",
  "type": "transcript.ready",
  "created_at": "2026-04-10T14:50:22Z",
  "data": {
    "meeting_id": "mtg_8f3k2j1m4n5p",
    "transcript_id": "trs_9a2b3c4d5e6f"
  }
}
```

| Field        | Type   | Description                                          |
| ------------ | ------ | ---------------------------------------------------- |
| `id`         | string | Unique event ID. Use this for idempotency.           |
| `type`       | string | The [event type](/api-reference/webhooks/events).    |
| `created_at` | string | ISO 8601 timestamp of when the event occurred.       |
| `data`       | object | Event-specific payload. Contents vary by event type. |

## Signature verification

Every webhook request includes a signature in the `X-Mavio-Signature` header. Always verify this signature to confirm the request came from Mavio and was not tampered with.

The signature is an HMAC-SHA256 hash of the raw request body, computed using the webhook secret you provided when creating the subscription.

### Verification steps

1. Read the raw request body (before any JSON parsing).
2. Read the `X-Mavio-Signature` header.
3. Compute the HMAC-SHA256 of the body using your webhook secret.
4. Compare the computed signature with the header value using a constant-time comparison.

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook(payload_body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode("utf-8"),
          payload_body,
          hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(f"sha256={expected}", signature)
  ```

  ```javascript Node.js theme={null}
  import crypto from 'crypto';

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

  ```go Go theme={null}
  package main

  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
      "fmt"
  )

  func verifyWebhook(payload []byte, signature, secret string) bool {
      mac := hmac.New(sha256.New, []byte(secret))
      mac.Write(payload)
      expected := fmt.Sprintf("sha256=%s", hex.EncodeToString(mac.Sum(nil)))
      return hmac.Equal([]byte(expected), []byte(signature))
  }
  ```
</CodeGroup>

<Warning>
  Always use constant-time comparison functions (`hmac.compare_digest`, `crypto.timingSafeEqual`) to prevent timing attacks. Never use `==` for signature comparison.
</Warning>

## Retry policy

If your endpoint does not respond with a `2xx` status code within **10 seconds**, Mavio will retry the delivery with exponential backoff:

| Attempt   | Delay after failure |
| --------- | ------------------- |
| 1st retry | 30 seconds          |
| 2nd retry | 2 minutes           |
| 3rd retry | 10 minutes          |
| 4th retry | 1 hour              |
| 5th retry | 6 hours             |

After 5 failed retries (approximately 7 hours total), the delivery is marked as failed. You can view failed deliveries and manually trigger redelivery from the [webhook dashboard](https://mavioapp.com/settings/webhooks).

### Automatic disabling

If your endpoint fails to respond successfully for **72 consecutive hours**, the webhook subscription is automatically disabled and you are notified by email. Re-enable it from the dashboard once the issue is resolved.

## Best practices

<AccordionGroup>
  <Accordion title="Respond quickly">
    Return a `200 OK` immediately after receiving the webhook and process the event asynchronously. Long-running processing should happen in a background job.
  </Accordion>

  <Accordion title="Handle duplicates">
    Use the `id` field for idempotency. In rare cases (network issues, retries), you may receive the same event more than once. Track processed event IDs to avoid duplicate handling.
  </Accordion>

  <Accordion title="Verify signatures">
    Always validate the `X-Mavio-Signature` header. Reject any requests with invalid or missing signatures.
  </Accordion>

  <Accordion title="Use a dedicated endpoint">
    Create a dedicated URL path for Mavio webhooks (e.g., `/webhooks/mavio`). This makes it easier to apply authentication, logging, and rate limiting specific to webhook traffic.
  </Accordion>
</AccordionGroup>

## Testing webhooks locally

During development, use a tunneling tool to expose your local server:

```bash theme={null}
# Using ngrok
ngrok http 3000

# Then register the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/mavio
```

You can also use the **Test** button on the [webhook dashboard](https://mavioapp.com/settings/webhooks) to send a sample event to your endpoint.
