Skip to main content
CallVA talks HTTP to your systems in three ways, and each has its own page section:

Call event webhooks

An agent forwards call.dialing, call.started, call.ended and call.recording to a URL you name.

Webhook schedules

Call a URL, or run an automation, at times decided by rules: weekdays, windows, intervals, dates.

Webhook logs

The record of every request around a call: dispatch, room, config fetch and each report the voice engine sent.
Everything below works with an API token whose role holds the matching permission: agents.update to configure an agent’s webhook, schedules.read / schedules.create / schedules.update / schedules.delete for schedules, webhook_logs.read for logs. See Authentication for tokens and roles.

Call event webhooks

A voice agent can forward the events of every call it handles to a URL of yours. You choose which of the four event types leave the platform; the rest are processed by CallVA as always and go nowhere else. Call event webhooks are produced for calls that run on CallVA’s own voice engine (the LiveKit worker), inbound and outbound.

How a call’s events travel

The voice worker reports each event to CallVA, signed with the project’s secret. CallVA verifies it, updates the call record (status, parties, timestamps, transcript, recording, billing), and only then forwards the report to your URL if the agent subscribes to that event type.

Configure the receiving URL

The URL lives on the agent. Three fields control it, set with Update an agent (PATCH /agents/{id}) or in the dashboard on the agent’s settings:
boolean
Turns forwarding on or off.
string
Where the events are posted. Any valid URL up to 2048 characters.
string[]
The event types to forward: any of call.dialing, call.started, call.ended, call.recording. The list you send replaces the stored one. An empty list forwards nothing, even with forwarding enabled and a URL set.
Response (trimmed)
The stored list comes back de-duplicated and in the order a call meets the events. An unknown event name is refused with 422:
Where to send, per call. An outbound call may name a different URL for itself in agent_overrides.custom_webhook_url; that call’s events go there. Which events leave is always read from the agent as it stands when each event arrives, so unsubscribing an event type stops it at once, even for calls already in progress.

Payloads

Each event is posted as a JSON object - the envelope the voice worker reported, with CallVA’s own credentials (signing secret, TURN password, LiveKit keys) removed and nothing else changed.
string
required
call.dialing, call.started, call.ended or call.recording.
string
required
Unique id of this report: <call id>:<event>:<milliseconds>.
number
required
When the report was built, Unix seconds with a fraction.
object
required
The call. id is the CallVA call id - use it with Get a call.
object
The agent configuration the call ran with, including any per-call override: id, name, prompt, greeting and timeout settings, custom_webhook_enabled, custom_webhook_url.
string | null
Which CallVA deployment served the call.
object
What the voice engine reported, verbatim: room, job, participant, sip (SIP attributes such as callStatus) and disconnect_reason. On call.ended it also carries session_report, with chat_history.items (the raw conversation) and usage (per-provider usage). Not present on call.recording.
object | null
On call.ended, where the recording will be stored: delivery, audio_key, session_report_key. On call.recording, the same with stored: true. Keys are object names, never URLs; play the audio through Stream a call’s recording.
array | null
What went wrong during the call, when anything did.
object | null
Session tags and outcome the agent recorded: tags, outcome, reason.
The transcript CallVA stores is built from livekit.session_report.chat_history, with consecutive pieces of one speaker merged into utterances. If you want the stored version rather than the raw one, read it with Get a call’s transcript.

Delivery: headers, timeout, retries

The forwarded request is not signed, so treat its body as a notification, not as proof. Use an HTTPS URL that is hard to guess (for example with a long random path segment), and before you act on an event, confirm it by reading the call with Get a call. Deduplicate on the envelope id: if CallVA fails while processing a report, the worker retries that report and your URL can see the same id twice.

The project’s webhook signing secret

Every project has a signing secret of its own (the agent_webhook_secret project setting). It authenticates the reports a voice worker sends to CallVA - the signed arrows in the diagram above - and only that project’s secret verifies that project’s calls. CallVA’s own worker receives the secret with each call’s configuration, so you need it only if you run your own LiveKit worker against CallVA, or relay reports to CallVA yourself. The worker signs each report like this:
CallVA recomputes the HMAC over the exact raw body, compares in constant time, and refuses with 401 when a header is missing (missing_signature), the timestamp is more than 300 seconds from its clock (timestamp_expired), the signature does not start with sha256= (invalid_signature_format), the call is unknown (unknown_call), or the HMAC differs (invalid_signature). The same check, in code:
To replace the secret, call Rotate the webhook signing secret. The old secret stops verifying at once, so reports from calls already running with it are refused. The answer confirms has_value and never contains the secret:
Response (trimmed)
Read the value with Reveal a project setting, which needs the project_secrets.read permission. Generating needs settings.update.

Webhook schedules

A webhook schedule calls one URL - or runs one automation - at the moments its rules allow. Use it for a nightly sync, a report every weekday morning, a heartbeat every five minutes inside office hours.

The model

A schedule is the target and how to call it: webhook_url, webhook_method (GET, POST, PUT, PATCH, DELETE), webhook_headers, webhook_payload, timeout_seconds (1-300, default 30), max_retries (0-10, default 3), retry_delay_seconds (0-3600, default 60) and is_active. With target_type: "automation" and an automation_id, it runs that automation instead of calling a URL. A rule says when. Each rule has a priority (lower runs first), an action (run or skip), and conditions that must all pass: execution_interval (seconds, default 60, at least 10 for run rules, at most 86400) sets the cadence inside a matching window. Times are anchored to time_from (or midnight): an interval of 300 from 09:00 fires at 09:00, 09:05, 09:10 and so on, with time_to inclusive. Rules are evaluated in priority order and the first rule whose conditions all pass wins. When no rule matches, nothing runs. A rule with is_exclusive: true and a date “owns” its dates: on those days no lower-priority rule is consulted, and outside its own time window the answer is skip. Use it for holidays and one-off days. Rules are evaluated in the schedule’s timezone: the project’s timezone setting, else the organization’s, else UTC.

Pre-schedule, then execute

A scheduler ticks every ten seconds and does three things in order. An execution moves through these states:
  • One run per moment. A schedule and a run time identify an execution uniquely, so overlapping ticks cannot create or send the same run twice.
  • No replay after an outage. If the scheduler was down and several runs of a schedule are due at once, only the newest is sent; the older ones are marked skipped with Skipped: newer execution available (heartbeat gap recovery).
  • Deactivating cancels planned runs. Executions already pre-scheduled re-check the schedule and rule when they come due and are marked skipped if either was switched off (Schedule was deactivated before execution), without any request.
  • max_retries: 0 means one attempt. Connection errors, timeouts and non-2xx answers all count as failures.
  • What is sent. POST, PUT and PATCH send webhook_payload as JSON (with Content-Type: application/json unless your headers set one); GET and DELETE send no body. Your webhook_headers are sent as given. Scheduled requests are not signed: put a credential for your endpoint in webhook_headers.
  • Automation targets run the automation with no arguments, so every parameter of its main needs a default.

Create a schedule

Create a webhook schedule:
Response 201 (trimmed)
Always send webhook_headers and webhook_payload, as {} when you have none. Leaving either out is answered with a 500 (Not null violation) and nothing is created.
Schedule names are unique within a project. Header values are stored as given and returned to anyone who can read the schedule, so give schedules.read only to roles that may see the credentials you put there.

Preview a moment

Preview a webhook schedule tells you which rule governs a moment and whether it runs. It checks the rule conditions, not interval alignment: a moment between two interval ticks still reports run if a run rule matches.
exclusive_block: true means an exclusive rule owns the date but the moment is outside its time window, so the answer is skip and no other rule was asked.

Read the execution history

List a schedule’s executions lists executions, upcoming (pre-scheduled) ones included. It is ordered by triggered_at descending, so runs not yet triggered come first. per_page defaults to 50.
Response (trimmed)
Executions keep the request and response bodies they carried. Finished ones (success, failed, skipped) are deleted once older than the organization’s webhook_execution_retention_days setting: 30 days by default, between 7 and 90. An execution that stays in processing means the worker stopped mid-call; nothing reclaims it.

Update, pause and delete

Update a webhook schedule is a full replacement: send every field you want to keep. Omitted settings return to their defaults (max_retries 3, timeout_seconds 30, is_active true), and omitted rules are deleted. Send a rule’s id to keep and update it; a rule without an id is created.
Response (trimmed)
Setting is_active: false pauses the schedule: no new runs are pre-scheduled, and runs already planned are marked skipped when they come due. Delete a schedule removes it with its rules and execution history:
List all schedules (per_page defaults to 15) and get one return schedules with their rules in priority order.

Webhook logs

Webhook logs are a project’s record of the HTTP exchanges around its calls, kept for debugging. Each entry stores the full request (method, URL, headers with credentials redacted, payload, source IP), the response (status, body, time taken), a processing status, an error message, and the call it relates to. Entries come from these exchanges: processing_status is success, validation_failed, processing_error or signature_invalid. Forwards to your custom webhook URL and scheduled webhook requests are not webhook-log entries; the latter have their own execution history.

List and filter

List webhook logs, newest first:
Response (trimmed)
List webhook log filters returns the values to offer, including the event types that actually occur in this project:

One call’s logs

List a call’s webhook logs takes the same query parameters and answers 404 when the call is not in the project:
This call was created through the API rather than placed by the voice engine, so nothing was exchanged for it. For a placed call you see its dispatch steps, the configuration request and every lifecycle report, in order.

One entry

Get a webhook log returns one entry with the same fields, plus related_call when it names a call:
Response (trimmed)
Request payloads can contain personal data such as phone numbers, and the log keeps them as received. Give webhook_logs.read only to roles that should see that. Entries are deleted once older than the organization’s webhook_log_retention_days setting: 30 days by default, between 7 and 90.

Automations

Point a call event webhook or a schedule at a script that runs inside CallVA.

Calls

The call record that events update and that webhook logs relate to.