Webhooks
Subscribe to call lifecycle events with retry, HMAC signing, and a delivery log.
osmTalk POSTs lifecycle events to your URL so your CRM, dashboards, and automations stay in sync without polling.
Configure
Settings → Webhook has four fields:
- Webhook URL — where events are POSTed. Leave blank to disable all webhooks.
- HMAC secret — when set, requests include
X-OsmTalk-Signature: sha256=…so you can verify the payload came from osmTalk. - Events — which events fire. Default = all events (including ones we add later).
- Send Test — fires a
webhook.testping immediately so you can verify your endpoint before going live.
Available events
| Event | When it fires | Payload includes |
|---|---|---|
call.started | A call connects (web, phone, or WhatsApp) | callId, agentId, channel, startedAt |
call.completed | A call ends with status=completed | callId, durationSeconds, costTotal, transcript |
call.failed | A call ends with status=failed | callId, error, failedAt |
call.analysis_completed | Post-call analysis finishes | callId, analysis (sentiment, summary, custom fields) |
campaign.lead_completed | Outbound campaign lead reaches a terminal state | campaignId, leadId, status, outcome, callId |
transfer.initiated | Bot transfers the call to a human or another agent | callId, destination, summary |
webhook.test | You clicked "Send Test" | test payload |
Delivery guarantees
- First attempt is synchronous. If your endpoint responds 2xx within 15s, we mark the delivery successful.
- Retries on 5xx, 408, 429, or network errors. Backoff is Stripe-style: +1m, +5m, +30m, +2h, +12h. After 6 failed attempts, the event moves to a dead-letter list.
- 4xx (except 408/429) is a permanent failure. We assume your endpoint is misconfigured and won't retry, so a typo in the URL doesn't burn through your retry budget. Fix the issue and re-send manually.
Signature verification
Every request signed with your secret carries this header:
X-OsmTalk-Signature: sha256=<hex-hmac>Verify in your handler (Node example):
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyOsmtalkSignature(rawBody: string, header: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}Always verify using the raw request body (before JSON parsing) and use a constant-time comparison.
Delivery log
Every attempt — success or failure — shows up in Settings → Webhook → Recent deliveries:
- Event name, status code, attempt number, latency, timestamp
- Last 50 attempts (Redis-backed, 30-day TTL)
- Failed attempts include the error reason (
HTTP 500,timeout,econnrefused)
Use this when:
- A customer says "I didn't get the webhook" — check whether we tried, and what the response was
- Tuning retry behavior — see how often a flaky endpoint fails
- Debugging signature mismatches — the request was sent, you'll see your 401 response in the log
Event subscription filter
By default we send every event. If you only want certain events:
- Uncheck the events you don't need
- Save
The filter is honored at delivery time — disabled events are dropped before they reach the retry queue, so they don't count against your bandwidth or our retry budget.
Leaving all events checked means you'll automatically receive new event types we add later (e.g., analysis.alert_triggered if we ship it). If you switch to an explicit list, you opt out of that auto-subscribe.
Headers we send
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | osmTalk-Webhook/1.0 |
X-OsmTalk-Event | The event name, e.g. call.completed |
X-OsmTalk-Delivery-Attempt | 1 for first try, incremented on retry |
X-OsmTalk-Signature | HMAC-SHA256 (only if secret is configured) |
Respond 200 OK (any 2xx) to acknowledge. Anything else triggers the retry path.