# Webhooks

*Updated 2026-10-20*

**Availability:** *(To be confirmed.)*

A webhook sends each finished meeting to a URL you control, as JSON, signed
so you can verify it came from meaty.

## Add a webhook

In the web or desktop app: **Settings → Developers → Webhooks**.

1. Enter your endpoint URL. It must be **HTTPS** and publicly reachable.
2. Choose what's included: **Summary**, **Actions**, **Transcript**
   (transcript off by default).
3. Optional: **Auto-send when a meeting finishes** (off by default).
   Otherwise send meetings by hand from **Share**.
4. Copy the **signing secret** (`whsec_…`). It's shown once.

Up to 8 webhooks per account. **Send test** delivers a sample payload any
time.

## The request

`POST` to your URL with a JSON body and these headers:

| Header | Value |
|---|---|
| `X-Meaty-Event` | `meeting.ready` or `meeting.updated` |
| `X-Meaty-Delivery` | A delivery ID, the same across retries — use it to ignore duplicates |
| `X-Meaty-Signature` | `sha256=` + hex HMAC-SHA256 of the raw body |

`meeting.ready` fires when the notes are first ready. `meeting.updated`
fires after edits (batched over a few seconds) — including the template
being applied, which usually lands moments after `meeting.ready`.

## The body

```json
{
  "event": "meeting.ready",
  "meeting": {
    "id": "6f0c…",
    "title": "Quarterly planning with Acme",
    "created_at": "2026-10-20T09:30:00+00:00",
    "duration_ms": 1800000,
    "meeting_type": "client_consultation",
    "tags": ["acme"],
    "url": "https://meaty.me/public/…",
    "summary": "…",
    "description": "…",
    "actionables": [
      {"task": "Send the revised quote", "owner": "Maria", "due_date": "2026-10-24", "status": "open"}
    ],
    "transcript": "[…] Maria: …\n[…] Tom: …"
  }
}
```

- A part you switched off is **absent**, not empty.
- `url` is present only when the meeting has an active share link.
- `transcript` is cut at 200,000 characters.
- A test delivery adds `"test": true`.

## Verify the signature

The key is the whole signing secret, `whsec_` included. Compute over the
raw request bytes, before any JSON parsing:

```python
import hashlib, hmac

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

## Delivery and retries

- meaty waits 5 seconds for a `2xx`. Redirects aren't followed.
- A failed delivery is retried twice: after 1 minute, then after 10.
- The last deliveries and their results are listed on the webhook.

See [Integration delivery failures](/docs/integration-delivery-failures).
