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

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"
  }'
Response
{
  "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"
}
Your webhook URL must be publicly accessible and support HTTPS. HTTP endpoints are rejected in production.

Manage webhook subscriptions

ActionMethodEndpoint
List all webhooksGET/v1/webhooks
Get a webhookGET/v1/webhooks/:id
Create a webhookPOST/v1/webhooks
Update a webhookPATCH/v1/webhooks/:id
Delete a webhookDELETE/v1/webhooks/:id

Webhook payload format

Every webhook delivery sends a JSON payload with the following structure:
{
  "id": "evt_f6g7h8i9j0",
  "type": "transcript.ready",
  "created_at": "2026-04-10T14:50:22Z",
  "data": {
    "meeting_id": "mtg_8f3k2j1m4n5p",
    "transcript_id": "trs_9a2b3c4d5e6f"
  }
}
FieldTypeDescription
idstringUnique event ID. Use this for idempotency.
typestringThe event type.
created_atstringISO 8601 timestamp of when the event occurred.
dataobjectEvent-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.
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)
Always use constant-time comparison functions (hmac.compare_digest, crypto.timingSafeEqual) to prevent timing attacks. Never use == for signature comparison.

Retry policy

If your endpoint does not respond with a 2xx status code within 10 seconds, Mavio will retry the delivery with exponential backoff:
AttemptDelay after failure
1st retry30 seconds
2nd retry2 minutes
3rd retry10 minutes
4th retry1 hour
5th retry6 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.

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

Return a 200 OK immediately after receiving the webhook and process the event asynchronously. Long-running processing should happen in a background job.
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.
Always validate the X-Mavio-Signature header. Reject any requests with invalid or missing signatures.
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.

Testing webhooks locally

During development, use a tunneling tool to expose your local server:
# 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 to send a sample event to your endpoint.