Webhooks
End-of-call report
The webhook OnCore sends to your server when a call finishes.
When a call ends, OnCore POSTs an end-of-call-report event to your server with everything about the call: transcript, summary, timing, recording, and outcome data. This is the primary way to sync call results into your own systems.
Delivery#
The report is sent as an HTTP POST with a JSON body to your configured webhook URL:
- The assistant's
serverUrlif set (per-assistant override), otherwise - your tenant-level server URL.
Delivery is currently not retried: if your endpoint is down or returns a non-2xx status, that report is not redelivered. Respond quickly with a 2xx and process asynchronously.
Verifying requests#
Every delivery includes your client server secret (shown on the API Keys page) in a header. Compare it against the value you have on file and reject mismatches:
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
x-sadie-core-secret: YOUR_CLIENT_SERVER_SECRETapp.post("/sadie/webhooks", (req, res) => {
if (req.header("x-sadie-core-secret") !== process.env.SADIE_CLIENT_SERVER_SECRET) {
return res.status(401).end();
}
const event = req.body;
if (event.type === "end-of-call-report") {
// persist transcript, summary, recording_url, ...
}
res.status(200).end();
});Payload#
| Field | Type | Description |
|---|---|---|
type | string | Always end-of-call-report |
timestamp | number | Unix time in milliseconds when the event was produced |
call_id | string | Unique call identifier |
tenant_id | string | Your tenant |
assistant_id | string | The assistant that handled the call |
customer_number | string | The caller's number |
restaurant_number | string | The OnCore number that was called |
transcript | string | Full call transcript |
summary | string | AI-generated call summary |
messages | array | Turn-by-turn messages |
duration_seconds | number | Call duration in seconds |
duration_minutes | number | Call duration in minutes |
started_at / ended_at | string | Call start / end times |
recording_url | string | Call recording location ("NA" when no recording is available) |
category | string | Primary call category |
category_list | array | All matched categories |
transfer_reason_id | string | Set whenever a transfer was attempted during the call (regardless of ended_reason); omitted when no transfer occurred |
supervisor_answered | boolean | null | Transfer outcome: true if the transferred call was answered (voicemail counts as answered), false if it went unanswered (no-answer, busy, failed, or canceled), null when there is no transfer outcome to report (typically the call had no transfer) |
ended_reason | string | Why the call ended. An open string; see Why a call ended below. |
extra | object | Additional outcome data (tags, CSAT rating/feedback, SMS reason) |
A companion assistant-request event fires when a call starts, with the same identifiers (call_id, assistant_id, customer_number, call_source). It is useful for pre-loading customer context before the call ends.
Reacting to missed transfers#
On a warm-return transfer, supervisor_answered: false means the destination never picked up and the caller came back to the assistant. What the caller hears at that moment is configurable via the assistant's warmReturnMessage.
false is not the whole story for missed hand-offs
This field records whether the call connected, and a voicemail box connects, so a
destination whose answering machine picks up reports supervisor_answered: true. Warm-return
still returns the caller in that case (see Transfers), so alerting only on
false silently misses every hand-off that went to voicemail.
Call recording & transcripts explains what is captured and why. To build a receiver for this payload step by step, follow Receive call results on your server.
Transfers explains the three modes and which of them can produce a return.
Why a call ended#
ended_reason is an open string, not a closed enum, so treat an unrecognised value as
"other" rather than failing on it. Four values are set by OnCore itself and are the ones
worth branching on:
| Value | What happened |
|---|---|
assistant-ended-call | The assistant ended the call deliberately: it decided the conversation was finished. |
assistant-forwarded-call | The call was handed to a transfer destination. The assistant's leg ended because the call left it, which is a success, not a failure. |
silence-timed-out | The caller stopped responding and idle handling ran out. Tunable with idleTimeoutSeconds and idleMessageMaxSpokenCount. |
agent-waited-alone | Nobody ever joined. The assistant answered and the far end never connected. Worth alerting on, because it usually means a routing or telephony problem rather than a caller who hung up. |
Anything else comes from the telephony layer and is passed through unchanged: normal
hang-ups, unreachable numbers, and trunk failures all surface here under their own names.
One more OnCore value, disconnect_reason_null, means the call ended without the underlying
layer reporting a cause. It is rare, and not itself a fault.
Do not treat assistant-forwarded-call as a dropped call
It is the most commonly misread value. A successful transfer always ends the assistant's
leg, so a dashboard counting "calls that ended early" will count every hand-off unless it
excludes this. Pair it with transfer_reason_id, which is set whenever a transfer was
attempted, and with the caveat about supervisor_answered above.
Next steps#
- Verifying webhooks: confirm the delivery really came from OnCore.
- Assistant request: the delivery at the start of the call.
- Read call activity: pull the same data on demand instead.