Webhooks
Webhooks push results to you as they happen, so you don’t have to poll. Midwater sends a signed POST with a JSON body to your HTTPS endpoint.
Setting up an endpoint
Section titled “Setting up an endpoint”- In the app, open Routes & destinations and add a webhook destination with your endpoint’s URL. The URL must be publicly reachable; private, loopback and link-local addresses are refused.
- A webhook destination receives
conversation.evaluated,agent.health_changedandgroup.health_changed, pluscheck.failedfrom any route that sends to it. It gets live conversations only, unless you tick Also send events from test calls. - Copy the environment’s signing secret (
whsec_…) from the Developers page and verify every delivery. - Press Send test event to receive a
testevent.
Events
Section titled “Events”| Event | Sent when |
|---|---|
conversation.evaluated |
A conversation finished scoring. Carries the outcome and every result. |
check.failed |
An active check failed on a conversation, and a route sends that check (or that severity) to this destination. |
check.failed.digest |
Instead of check.failed, when the route delivers as an hourly digest. |
agent.health_changed |
An agent’s health changed, with watch or at_risk as the old or new status. |
group.health_changed |
The same, for a group. |
test |
Someone pressed Send test event. |
check.failed comes only from routes: a route says which checks or severities, for which agents or groups, go to which destination. Shadow checks never trigger it.
Payload
Section titled “Payload”Every event has the same envelope:
{ "id": "evt_a1b2c3d4e5", "type": "conversation.evaluated", "environment": "live", "created_at": "2026-10-08T21:12:00.000Z", "data": { "conversation_id": "cmv0187nm005po3016rag4dcg", "external_id": "call_8f2a91", "agent": { "id": "front-desk", "name": "Front desk", "health_status": "healthy" }, "group": { "id": "brightsmile-dental", "name": "BrightSmile Dental" }, "outcome": "resolved", "results": [ { "check_key": "need_unresolved", "verdict": "pass", "score": 0.04, "decided_by": "model", "shadow": false } ] }}environment is test or live. data.agent is on every conversation and health event, with data.group when the agent has one. The full schema for each event is in the API reference.
A check.failed event:
{ "id": "evt_9f8e7d6c5b", "type": "check.failed", "environment": "live", "created_at": "2026-10-08T21:12:01.000Z", "data": { "conversation": { "id": "cmv0187nm005po3016rag4dcg", "external_id": "call_8f2a91", "channel": "voice", "agent_version": "3.2.0", "ended_at": "2026-10-08T21:11:40.000Z" }, "agent": { "id": "front-desk", "name": "Front desk", "health_status": "watch" }, "check": { "key": "false_booking_confirmation", "name": "Agent confirmed a booking with no successful booking event", "severity": "high", "version": 1 }, "result": { "score": 1, "verdict": "fail", "decided_by": "rule", "reason": "Agent confirmed a booking, but no successful booking event exists." }, "url": "https://app.example/conversations/cmv0187nm005po3016rag4dcg" }}Health events carry from, to, both windows (last_7_days, last_30_days, as on the health endpoints) and a url. group.health_changed also names the agent whose status set the group’s.
Headers
Section titled “Headers”| Header | |
|---|---|
Midwater-Signature |
t=<unix seconds>,v1=<hex>. Verify it. |
Midwater-Event |
The event type, the same as type in the body. |
Midwater-Delivery |
The delivery ID. The same on every retry of one delivery: use it to ignore duplicates. |
User-Agent |
Midwater-Webhooks/1.0 |
Content-Type |
application/json |
Delivery and retries
Section titled “Delivery and retries”- Answer with any
2xxwithin 5 seconds. Do slow work after you answer, for example on a queue. - Today, anything else, or no answer in time, is retried 3 times, about 2, 8 and 30 seconds apart. After that the delivery is marked failed.
- Planned Retries will move to 8 attempts over about 24 hours, so an endpoint that’s down for a few hours still gets its events.
- Every attempt, with its status and timing, is in the destination’s delivery log in the app.
- Deliveries can arrive more than once and out of order. Deduplicate on
Midwater-Delivery, which is the same on every retry of a delivery, and usecreated_atand the conversation’s ownstatusrather than arrival order.