Webhooks
Register webhooks for meeting events, verify HMAC signatures, and deliver them safely.
A webhook is a callback: Meetily sends an HTTP POST to a URL you choose when an event happens, for example when a meeting summary is ready. Turn on Webhooks in the in-app Integrations panel before you register one.
Register a webhook
POST /v1/webhooks requires Webhooks to be enabled in the app. The route floor is the Read scope, but subscribing to any recording.* event escalates the requirement to the Record scope. Every other event clears on Read.
The body takes a url, an events array, and an optional secret. If you omit secret, Meetily generates one. Either way the signing secret is returned once, on creation. Store it; there is no endpoint to fetch it again.
{ "url": "https://example.com/hook", "events": ["summary.completed"], "secret": "optional" }curl -s -X POST http://127.0.0.1:8420/v1/webhooks -H "Authorization: Bearer $MEETILY_PRO_TOKEN" -H "content-type: application/json" -d '{"url":"https://example.com/hook","events":["summary.completed"]}'from meetily_agent import MeetilyClient
print(MeetilyClient().webhooks.create("https://example.com/hook", ["summary.completed"]))Signature verification (HMAC)
Every delivery is signed. Two headers ride with the POST:
| Header | Value |
|---|---|
X-Meetily-Signature | sha256=<hex> |
X-Meetily-Timestamp | Unix seconds |
The signature is HMAC_SHA256(secret, "{timestamp}.{body}"), rendered as lowercase hex. Compute it over the raw request body, not a re-serialized copy, and compare with a constant-time check.
import hmac
import hashlib
def verify_signature(secret: str, timestamp: str, raw_body: bytes, signature_header: str) -> bool:
signed_message = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed_message, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature_header)Target URL rules (SSRF)
The target must be a public, routable URL. Meetily rejects loopback and private addresses with 400, including 127.0.0.1, ::1, 10.x, 192.168.x, and 169.254.x.
You cannot register a localhost URL
http://127.0.0.1:... is always rejected, at registration time, whether or not the app and your receiver are on the same machine. To receive webhooks on your own machine, run a public HTTPS tunnel (for example ngrok or cloudflared) in front of your local receiver, and register the tunnel's public URL instead.
Delivery and retries
Delivery is at-least-once. On failure, Meetily retries with backoff of 1, 2, 4, 8, then 16 seconds, for up to six attempts total.
If every attempt fails, the deliveries_failed counter on GET /health increments, so a permanently broken endpoint is discoverable without polling each webhook individually. This is separate from events_dropped, which counts events lost before a delivery attempt was even made, for example because the delivery queue was full.
Managing webhooks
| Method + path | Scope | Notes |
|---|---|---|
GET /v1/webhooks | Read (Admin for ?all=true) | List your webhooks. |
GET /v1/webhooks/:id/deliveries | Owner or Admin | Delivery attempts for one webhook. |
POST /v1/webhooks/:id/test | Write | Sends a synthetic webhook.test delivery, one attempt, returns 202. |
DELETE /v1/webhooks/:id | Write (owner) or Admin | Removes a subscription. |
Webhooks are HTTP and Python-SDK only
Webhooks are managed over the HTTP API and the Python SDK. There is no webhook command in the CLI. The MCP webhook tools are hidden unless the server is started with --allow-webhooks. The app itself does not show a list of your registered webhook URLs. See Limitations for the full picture.
Last updated on
