Speko Docs
Build

Workspace webhooks

Route lifecycle events to organization endpoints by event, agent, and session tags, then inspect every delivery attempt.

Workspace webhooks are managed independently from agents. Configure them in Settings → Webhooks or through /v1/webhooks. One endpoint can subscribe to several lifecycle events and either every agent or an explicit agent set.

Create an endpoint

const endpoint = await speko.webhooks.create({
  name: 'Production call events',
  url: 'https://hooks.example.com/speko',
  events: ['call.status', 'call.report', 'call.analysis', 'call.recording'],
  allAgents: true,
  filterTags: { environment: 'production' },
  headers: { 'x-source': 'speko' },
  authHeaders: [{ name: 'Authorization', value: 'Bearer secret-token' }],
  timeoutMs: 4000,
});

Supported voice-session events are:

EventWhen it firesAutomatic retries
call.pre_callBefore a phone worker starts; the response may override supported call configurationNo
call.statusWhen a call lifecycle event is recordedNo; best effort
call.reportWhen the report is finalized, and again when a recording becomes readyYes
call.analysisWhen post-call analysis completesYes
call.recordingWhen recording reaches ready or failedYes

Call control events

These describe a human call — a broker on a browser softphone talking to a phone — leg by leg, rather than an AI voice session. None of them are retried: they are a live projection of the call's event history, which stays readable at GET /v1/voice/calls/{callId}/events, so a redelivery would arrive too late to be worth anything.

EventWhen it firesAutomatic retries
call.initiatedA call is created, before anyone is reachableNo
call.ringingA leg starts ringing (an outbound INVITE is out, or a broker is being rung)No
call.answeredA leg answers; the first one to answer also starts the callNo
call.bridgedA leg is pulled into another leg's conversationNo
call.hold / call.unholdA leg is parked or resumedNo
call.mute / call.unmuteA leg's own audio is muted or restored, server-sideNo
call.dtmf.sentSpeko sent tones towards a PSTN legNo
call.transfer.initiated / call.transfer.completed / call.transfer.failedA blind or warm transfer progressesNo
call.leg.hangupOne leg dropsNo
call.hangupThe whole call endsNo

Every call-control payload carries call_id, control_id (null for the two call-scoped events, call.initiated and call.hangup), event_id and occurred_at alongside the event's own fields. Human calls have no agent, so an endpoint scoped to specific agentIds never receives them — subscribe with allAgents.

Inbound DTMF (a key the far end pressed) is deliberately absent from this table. It arrives as an in-room data packet addressed to room participants, not to the webhook receiver, so it is observable by a connected softphone and not by the server — and a subscription that can never deliver is worse than no subscription.

Every delivery includes a stable webhook_id for that event occurrence and webhook_tags from the session. A report's initial and recording-ready emissions are separate occurrences with different IDs; retries and manual redeliveries of one occurrence reuse its ID.

Agent and tag routing

allAgents defaults to true. Set it to false and provide agentIds to narrow the endpoint. New agents automatically match all-agent endpoints.

Tags are free-form, case-sensitive string pairs. Attach them when creating an agent-backed browser, realtime, or phone session:

await speko.voice.dial({
  to: '+12015551234',
  agentId: 'agent_123',
  webhookTags: {
    environment: 'production',
    customer: 'acme',
  },
});

An endpoint matches when all of its filterTags exist on the session with equal values. If at least one tagged endpoint matches, Speko sends to every matching tagged endpoint and does not use untagged defaults. If none match, Speko falls back to all matching untagged endpoints. Agentless sessions reject webhookTags because they cannot resolve an agent-scoped route.

Only one effective call.pre_call endpoint may exist for an agent/tag route. Conflicting endpoint configuration is rejected, and session creation fails explicitly if inconsistent stored data would still make the route ambiguous.

Signing and secrets

Requests use Standard Webhooks headers and default to the workspace signing secret. Set signingSecretSource: 'custom' with a write-only signingSecret to override it for one endpoint. Secret auth-header values are encrypted and returned only as { name, configured: true }. On update, omit an existing auth-header value to keep it or supply a value to rotate it.

Query values, signatures, authorization, cookies, API keys, and credential-shaped headers are redacted from delivery logs. Response bodies are capped at 64 KiB and expose responseTruncated.

Extraction fields

Endpoints subscribed to call.report or call.analysis can request extractionFields. Speko executes one extraction pass using the union required by all matching endpoints, then includes only each endpoint's requested fields in its custom_data. Identical definitions deduplicate; conflicting definitions are rejected.

Delivery logs and redelivery

Use the Logs tab in Settings or the SDK:

const page = await speko.webhooks.deliveries.list({
  endpointId: endpoint.id,
  event: 'call.report',
  status: 'failed',
  limit: 50,
});

const detail = await speko.webhooks.deliveries.get(page.data[0]!.id);
await speko.webhooks.deliveries.redeliver(detail.id);

Logs contain the endpoint-specific request, sanitized URL and headers, HTTP result, duration, bounded response, error, routing tags, and chronological attempts. They are retained for 30 days. Private phone-number API deliveries for imessage.received, imessage.reaction_received, imessage.sent, imessage.delivered, and imessage.delivery_failed appear in the same Logs tab and API, using the stable Inkbox event ID.

Manual redelivery is available for unexpired workspace lifecycle events except call.pre_call. It uses the stored original payload and webhook ID with the endpoint's current URL and credentials, and adds an attempt to the existing delivery. A manual failure does not create a new automatic retry or cancel one that was already pending; after an earlier success, the delivery remains successful. Inkbox owns retry scheduling for imessage.* subscriber deliveries, so those records are view-only.

Migrating agent webhooks

Existing agent webhook configuration is backfilled to central endpoints and dispatch reads only those endpoints, avoiding duplicates. agent.webhooks reads and writes remain supported through the current SDK major version; legacy writes synchronize legacy-managed central endpoints. New integrations should use speko.webhooks exclusively.

On this page