Non-obvious behaviors and silent defaults for Vapi webhook delivery, server configuration, and credential resolution.
If you omit serverMessages on an assistant, these events are sent by default:
conversation-update, end-of-call-report, function-call, hang,
speech-update, status-update, tool-calls, transfer-destination-request,
handoff-destination-request, user-interrupted, assistant.started
This is NOT "nothing" — it's 11 event types. Set serverMessages explicitly if you want fewer.
These are two independent settings:
| Setting | Controls | Default |
|---|---|---|
server.timeoutSeconds |
Webhook delivery timeout (status-update, end-of-call-report, function tools using server.url, etc.) |
Platform default (see API reference) |
timeoutSeconds (apiRequest tool) |
Individual API request tool HTTP call timeout | Platform default (see API reference) |
timeoutSeconds (code tool) |
Code tool execution timeout | Platform default, capped at 30s |
They don't inherit from each other.
- Missing
server.url: No network call is made. No error is raised. - Server returns error: The error is logged and returned to the platform. For
tool-callsevents, Vapi still attempts to parse the error response body for tool results. - High failure rate: Under sustained delivery failures, Vapi may reduce delivery of high-frequency events (like
speech-update) while still attempting critical events (likeend-of-call-report).
A single failed POST to your server.url should not be the difference between getting an end-of-call-report and losing it. Configure an explicit backoffPlan on the server block so transient failures (502/503/504, network blips, brief handler restarts) get retried, while semantic errors that won't change on retry (4xx) short-circuit immediately.
server:
url: https://your-webhook-endpoint.example.com/vapi
timeoutSeconds: 20
backoffPlan:
type: exponential
maxRetries: 3
baseDelaySeconds: 1
excludedStatusCodes: [400, 401, 403, 404, 413, 422]Field meanings:
| Field | Effect |
|---|---|
type: exponential |
Delay doubles each retry. With baseDelaySeconds: 1 and maxRetries: 3 the schedule is roughly 1s → 2s → 4s (~7s wall clock). |
maxRetries |
Upper bound on retry attempts (not counting the initial request). |
baseDelaySeconds |
First retry delay; subsequent retries double from this base when type is exponential. |
excludedStatusCodes |
Status codes that skip retry entirely. The request fails immediately on receipt. |
Why exclude the 4xx family. A 4xx response means your server understood the request and rejected it for a deterministic reason — bad auth (401/403), missing route (404), validation failure (400/422), payload too large (413). Retrying produces the same response, just later. excludedStatusCodes tells Vapi to give up on those codes and reserve the retry budget for genuinely transient failures (5xx, network errors, request timeouts).
Recommendation: make backoffPlan part of the default whenever you define a server.url. The cost is one config block; the benefit is that the end-of-call-report event — your post-call analytics ground truth — survives a brief webhook hiccup instead of being silently lost. Keep server.timeoutSeconds shorter than your slowest downstream dependency so a hanging handler doesn't compound across retries.
If credentialId is set on the server or tool, that specific credential is used. If omitted, Vapi picks one from the call's available credentials automatically.
Recommendation: Always set credentialId explicitly when multiple credentials exist to avoid ambiguous selection.
server.secret (or legacy serverUrlSecret) is sent to your server as the X-Vapi-Secret header. This is a simple shared-secret comparison, not an HMAC signature.
Recommendation: Prefer server.credentialId / webhook credentials for new integrations when you need OAuth2, Bearer, or HMAC auth. Keep server.secret for simple endpoints that only need a shared-secret check. Do not also set X-Vapi-Secret manually in server.headers unless you intentionally want to override the generated value.
All webhooks POST a JSON body with this structure:
{
"message": {
"type": "<event-type>",
"timestamp": 1234567890000,
"call": { ... },
"phoneNumber": { ... },
"customer": { ... },
"assistant": { ... },
...event-specific fields
}
}The call, phoneNumber, customer, and assistant fields are included on most message types (except issue-specific messages).