A call ends, and a brief lands in your own system a second later. No polling, no waiting. Sayli sends the event to an endpoint you own, and your tools pick it up from there.
Webhooks are on the Team plan and up.
Events, pushed to you
You give Sayli an HTTPS endpoint. When something happens on a call, Sayli sends a signed POST to that endpoint. You decide what to do with it: file a ticket, post to a channel, update a record.
Endpoints must be public HTTPS URLs. Sayli checks the address before sending and will not deliver to internal or private network addresses.
What we emit
Two events are live today.
More events are planned, including meeting.started, meeting.ended, and action_item.created. They are not sent yet. Subscribing to them today does nothing until they ship, and shipped events are announced in the changelog.
The delivery
Every delivery is a POST with Content-Type: application/json and a sayli-webhook/1.0 user agent, plus these headers:
brief.generated.The body always has the same three fields: the event name, when it happened, and the event's data.
{
"event": "brief.generated",
"occurred_at": "2026-06-29T15:42:10.482910+00:00",
"data": {
"meeting_id": "0d9e07a2-5b1f-4c8e-9f3a-2f6d1c8b4a70",
"title": "Weekly pipeline review",
"headline": "Renewal is on track, security review is the open risk",
"summary": "…",
"action_items": ["Send the SOC 2 report to Dana"],
"confidence": 0.86
}
}Verify the signature
The signature covers the timestamp and the raw body, joined by a dot: HMAC-SHA256(secret, "{Sayli-Timestamp}.{body}"), hex-encoded, in the Sayli-Signature header. Signing the timestamp lets you reject replays, not just tampering.
To verify a delivery:
- Read the raw request body before parsing any JSON.
- Compute the HMAC over
timestamp + "." + bodywith your endpoint's secret. - Compare against
Sayli-Signaturewith a constant-time comparison. - Reject the delivery if the timestamp is older than your replay window.
import hashlib
import hmac
import time
def verify(headers, raw_body: bytes, secret: str) -> bool:
timestamp = headers["Sayli-Timestamp"]
signature = headers["Sayli-Signature"]
signed = timestamp.encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
return False
return abs(time.time() - int(timestamp)) <= 300 # 5 minute replay windowTreat a missing or wrong signature as a request you did not send. Do not process the body.
When deliveries fail
Delivery is one attempt, and there are no retries yet. If your endpoint is down or slow (Sayli waits up to 8 seconds), that delivery is gone. Anything a webhook tells you can also be read back from the API, so a missed brief.generated is recoverable by fetching the brief.
After 10 failures in a row, Sayli disables the endpoint automatically so events stop piling up against a dead URL. Fix the endpoint, then turn it back on from settings.
Respond with a 2xx as soon as you receive the event, then do the slow work in the background. A response that beats the 8 second window is a delivery that counts.