Skip to content
GitHub
Get started →

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 → WebhooksAdd endpoint.

Fields:

FieldNotes
URLMust be https:// (localhost allowed in test mode)
EventsCheck the boxes you want to subscribe to
SecretAuto-generated. Used to sign every delivery. Copy now — shown once.

Or via the API:

POST /v1/webhooks
Authorization: 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.

EventWhen it firesStatus
conversation.endedA voice call finished (visitor hung up or session timed out)shipped
conversation.recording_readyAudio recording uploaded and recording_path populated (separate from ended because upload is async)shipped
lead.capturedThe agent called submit_lead with visitor contact detailsshipped
lead.updatedA PATCH /v1/public/leads/:id happened — echoes status/notes changes back to your other toolsshipped
session.startedVisitor opens a voice sessionreserved
function.calledAI called a toolreserved
usage.thresholdMonthly minutes cross 50%, 80%, 100%reserved
site.deletedA site was permanently removedreserved
adapter.errorData connection test failed in the backgroundreserved

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: 1745010000000
X-Spelo-Event: session.ended
X-Spelo-Delivery: del_pqr

Verifying signatures

We sign the request body with HMAC-SHA256 using your webhook’s secret. Verify in your handler:

// Node / Express
import 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/:id
Authorization: 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