Skip to main content

Webhook Setup Guide

Configuring webhooks lets you deliver call status changes, error events, and other notifications to external systems (Slack / Microsoft Teams / Email / a custom HTTPS endpoint) in real time.

Quickstart​

To verify connectivity in the shortest possible path, follow these three steps.

1. Minimal Node.js receiver​

import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
const { data, errors } = req.body;

// Error detection is based on `errors`, NOT `data.callStatus`.
if (Array.isArray(errors) && errors.length > 0) {
console.error(`callId=${data.callId} has errors`);
} else {
console.log(`callId=${data.callId}`);
}

// You must return 2xx. Otherwise the delivery is retried with exponential backoff.
res.sendStatus(200);
});

app.listen(3000);

2. Register the endpoint in the dashboard​

In the Recho dashboard's Webhooks page, register the URL above as a Custom endpoint.

3. Enable the triggers​

In the Notification Triggers section, pick the events (e.g. OUTBOUND_CALL_ERROR) you want to receive.

That's the minimal setup you need to verify connectivity. Continue reading below for full production setup.

Full setup flow​

  1. Register a destination endpoint. In the dashboard's Webhook page, choose Create new under Destination Endpoints and pick an endpoint type (Slack / Teams / Email / Custom) and the destination URL or email address.

  2. Pick an authentication method (Custom only). Custom endpoints support Bearer token authentication or RSA signature verification. See Authentication for details.

  3. Enable the triggers. In the Notification Triggers section, choose which events (e.g. outbound call completed, error, inbound call established) should fire a webhook.

  4. Implement your receiver. See the Payload Spec for the payload structure and the Sample Receivers page for reference implementations.

  5. Verify and operate. Place a real call or fire a test event to confirm connectivity.

Supported endpoint types​

KindNotes
SlackSupply an Incoming Webhook URL; the formatted message is posted to the Slack channel.
Microsoft TeamsSupply a Workflows / Incoming Webhook URL; the message is posted to the Teams channel as an Adaptive Card.
EmailSupply a destination email address; the formatted body is delivered by email.
CustomAny HTTPS endpoint. JSON is POSTed; supports authentication (Bearer / RSA signature). The most flexible integration option.

Besides notification destinations, there are endpoint kinds for triggering post-call work (ACW / After Call Work) agents.

KindNotes
Recho AnalyzerTriggers Recho's built-in call analysis agent. No destination to configure — Recho resolves it from the project (target is null in the payload).
Recho EvaluatorTriggers Recho's built-in call evaluation agent. Likewise no destination to configure (target is null in the payload).
Self Provided AgentSupply the HTTPS endpoint of your own agent. The agent records its result through POST /v1/acw-results.

Call-log trigger (CALL_LOG_CREATED)​

Most notification triggers are per call status — OUTBOUND_CALL_COMPLETED, INBOUND_CALL_ERROR, and so on. CALL_LOG_CREATED is different: it fires when a call transitions to one of a fixed set of call statuses that mean the call has ended (listed below), for outbound and inbound calls alike.

If your use case is "start something whenever a call finishes, regardless of its outcome" — most notably triggering an ACW agent — enabling this single trigger replaces enabling several per-status ones.

Call statuses that fire it​

DirectionCall statuses
OUTBOUNDCOMPLETED / VOICEMAIL_REACHED / ERROR / BUSY / NO_RESPONSE / UNREACHABLE
INBOUNDCOMPLETED / ERROR / CONCURRENCY_LIMIT_EXCEEDED

Every other call status leaves the trigger silent: calls that ended without being placed (CANCELED, EXPIRED, MAX_ATTEMPTS_REACHED, HEALTHCHECK_FAILED), in-progress statuses (CALLING, CLOSING), and the internal statuses that can still appear in data.callStatus. See Call statuses for the full list.

Make sure your receiver can tell the statuses apart

CALL_LOG_CREATED fires for statuses other than COMPLETED. If your receiver — an ACW agent, for example — assumes "the call completed normally" without reading data.callStatus, it will mishandle busy and unreachable notifications. Enable this trigger only after confirming that your receiver handles every status it may see.

The call log may not be readable when the notification arrives

This trigger fires on a call status transition. It does not observe the call log being stored, so both of the following happen:

  • The call log arrives late. The call log at data.callId may not exist yet when the notification reaches you — most notably for inbound calls and for outbound calls that ended busy or unreachable. Here, waiting works.
  • The call log is never created. Outbound calls whose dialling itself failed (telephony provider errors, calling-permission errors, a failed pre-call health check) are finalized as COMPLETED / ERROR, but no call was placed, so no call log is written. The same applies to inbound calls that ended abnormally mid-call and were later finalized to ERROR by stale-status detection. Waiting will never produce a call log in either case.

Because both "late" and "never" occur, a receiver that reads the call log or its transcript needs a give-up condition in addition to retries — after N attempts or T seconds, proceed without the call log.

The converse also happens: a call log can be created without this trigger firing at that moment (for example when inbound call acceptance fails and the call's own status is never updated). Such calls are later finalized to ERROR by stale-status detection, so the trigger can fire much later. If you decide "no notification within N minutes means it will never come", a long-delayed firing will cause double processing.

In any case, do not rely on "exactly one notification for every call" for auditing or reconciliation.

Telling outbound from inbound​

The trigger name carries no OUTBOUND_ / INBOUND_ prefix, so data.direction carries OUTBOUND or INBOUND instead. CALL_LOG_CREATED is the only trigger with this field.

{
"data": {
"callId": "abc-123",
"callStatus": "BUSY",
"callSid": "CA94b51...",
"clientId": "client-1",
"direction": "OUTBOUND"
}
}

data always carries these five keys, for outbound and inbound alike (missing values are empty strings). That matches the outbound per-status triggers plus direction. The inbound per-status triggers carry fewer keys (INBOUND_CALL_* carry callId / callStatus, and INBOUND_CALL_ERROR adds callSid), so the key sets do not line up there. See Payload Spec for details.

Using it alongside per-status triggers​

CALL_LOG_CREATED coexists with the existing per-status triggers. Subscribing to both for the same transition delivers two notifications for that call (for example a call ending COMPLETED with both OUTBOUND_CALL_COMPLETED and CALL_LOG_CREATED enabled). Enable only one of them if you do not want the duplicate — in particular, an ACW agent subscribed to both runs twice.

data.callId carries the same value in both, so a receiver can correlate the two notifications.

Delivery behavior​

  • Asynchronous delivery: Deliveries run independently of the originating API request. Webhook failures never affect the API response.
  • Per-call FIFO ordering: Events for the same callId are delivered in the order they were emitted (FIFO). A FIFO queue is used internally, so a call's state transitions (e.g. OUTBOUND_CALL_CALLING → OUTBOUND_CALL_COMPLETED) will never arrive reversed. Ordering across different calls is not guaranteed.
  • Automatic retries on failure: A receiver that returns 408, 429, 5xx, or times out is retried up to 6 times with exponential backoff. See Retry Policy for full details.