Webhooks
Spelo can POST events to an endpoint of yours when things happen. Useful for:
- Streaming session data into your warehouse
- Alerting your ops channel when usage spikes
- Syncing usage to your own billing system
- Marking a customer record “voice-engaged” in your CRM
Configuration
Dashboard → Webhooks → Add endpoint.
Fields:
| Field | Notes |
|---|---|
| URL | Must be https:// (localhost allowed in test mode) |
| Events | Check the boxes you want to subscribe to |
| Secret | Auto-generated. Used to sign every delivery. Copy now — shown once. |
Or via the API:
POST /v1/webhooksAuthorization: Bearer vk_live_...Content-Type: application/json
{ "url": "https://yourapp.com/hooks/spelo", "events": ["session.ended", "usage.threshold"], "description": "Warehouse sync"}Event types
The events that ship today are highlighted. Others are reserved for future expansion.
| Event | When it fires | Status |
|---|---|---|
conversation.ended | A voice call finished (visitor hung up or session timed out) | shipped |
conversation.recording_ready | Audio recording uploaded and recording_path populated (separate from ended because upload is async) | shipped |
lead.captured | The agent called submit_lead with visitor contact details | shipped |
lead.updated | A PATCH /v1/public/leads/:id happened — echoes status/notes changes back to your other tools | shipped |
session.started | Visitor opens a voice session | reserved |
function.called | AI called a tool | reserved |
usage.threshold | Monthly minutes cross 50%, 80%, 100% | reserved |
site.deleted | A site was permanently removed | reserved |
adapter.error | Data connection test failed in the background | reserved |
Event payload
{ "id": "evt_abc123", "type": "session.ended", "created_at": "2026-04-17T14:22:10.000Z", "site_id": "ab1c2d3e", "data": { "session_id": "sess_xyz", "duration_seconds": 124, "function_calls": 7, "db_queries": 3, "cost_cents": 42 }}Every delivery also carries headers:
X-Spelo-Signature: sha256=a7f...X-Spelo-Timestamp: 1745010000000X-Spelo-Event: session.endedX-Spelo-Delivery: del_pqrVerifying signatures
We sign the request body with HMAC-SHA256 using your webhook’s secret. Verify in your handler:
// Node / Expressimport crypto from 'crypto'
app.post('/hooks/spelo', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.header('X-Spelo-Signature') || '' const expected = 'sha256=' + crypto .createHmac('sha256', process.env.SPELO_WEBHOOK_SECRET!) .update(req.body) .digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { return res.status(401).end('bad signature') }
const event = JSON.parse(req.body.toString()) // ... handle event ... res.status(200).end()})Replay protection
Check X-Spelo-Timestamp is within 5 minutes of now. Reject older events to prevent replay attacks.
Delivery semantics
- At least once. Your endpoint may receive the same event twice. Dedupe by
event.id. - Ordered per site_id, best-effort. For strict ordering, order by
event.created_at. - Retries — exponential backoff (1m, 5m, 30m, 2h, 6h, 12h). We give up after 24 hours of failures.
- Rate limit — we cap delivery to 100/sec per endpoint. If your handler is slow, we throttle.
Expected response
Your handler should return a 2xx status within 10 seconds. Anything else (4xx, 5xx, timeout) is treated as failure and triggers a retry.
For long-running processing, ack the delivery immediately (200 OK) and enqueue the work.
Failures and alerts
If an endpoint fails repeatedly, we disable it and email you. The dashboard shows recent delivery status, response codes, and error bodies.
Deleting webhooks
DELETE /v1/webhooks/:idAuthorization: Bearer vk_live_...Test mode
In the dashboard, click Send test event to fire a synthetic event at your endpoint. Useful during setup.
Scopes
To manage webhooks via the API, your key needs the webhooks:write scope.
See also
- Authentication
- Analytics endpoint — alternative: pull instead of push
- Billing → Usage metering