From 373bf21103abde975f936a0ec59a2f377fac7274 Mon Sep 17 00:00:00 2001 From: Jeff Hoekman Date: Tue, 8 Sep 2026 13:24:06 -0400 Subject: [PATCH 1/3] VAP15-54 docs: document subsystem server credentials Extends VAP15-53 to the webhooks that go to a subsystem's own server rather than the assistant's webhook url - tools, knowledge bases, custom voices, dynamic handoff and transfer destinations, and custom endpointing providers. The rule is provenance inheritance: a subsystem server follows the assistant carrying it. A saved assistant keeps org credentials; a transient assistant or assistantOverrides is caller-authored and gets none. Calls out the mixed case, a saved assistant with an overridden voice, tools or knowledge base. Also gives the intelligent-routing handoff example an Authorization header. It was the only dynamic-destination example teaching a bare unauthenticated server url, directly under one that has auth. That content now lives in squads/handoff/destinations.mdx, which main split out of the handoff page. Provisional: VAPSEC-627 (#19542) is unreviewed and still changing. Re-verify the table and the inheritance rule against the merged behavior before this ships. Refs VAP15-54, VAPSEC-627 Co-Authored-By: Claude Opus 5 --- fern/server-url/server-authentication.mdx | 19 +++++++++++++++++++ fern/squads/handoff/destinations.mdx | 9 ++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/fern/server-url/server-authentication.mdx b/fern/server-url/server-authentication.mdx index ba90e241a..fcdd34804 100644 --- a/fern/server-url/server-authentication.mdx +++ b/fern/server-url/server-authentication.mdx @@ -405,6 +405,25 @@ If you supply the server URL in your API request, your endpoint must authenticat For webhooks sent to a server URL supplied in the API request, Vapi also strips credential fields from the payload. Any `apiKey`, `secret`, `password`, `authToken`, or similar field on the embedded `assistant`, `session`, or `chat` object arrives as the string `"[REDACTED]"` rather than its real value. The rest of the payload is unchanged. +#### Subsystem server URLs + +Some webhooks go to a URL owned by a subsystem rather than to your assistant's webhook URL - a tool, a knowledge base, a custom voice, a dynamic handoff or transfer destination, or a custom endpointing provider each carry their own `server` block: + +| Webhook | Server it is sent to | +| --- | --- | +| `tool-calls`, `function-call` | the tool's | +| `knowledge-base-request` | the knowledge base's | +| `voice-request` | the custom voice's | +| `handoff-destination-request` | the handoff destination's | +| `transfer-destination-request` | the transfer destination's | +| `call.endpointing.request` | the endpointing provider's | + +**These follow the assistant that carried them.** A tool on a saved assistant is org-configured and keeps your credentials. The same tool on a transient assistant or in `assistantOverrides` is caller-authored, and Vapi sends to its URL without them. + + +A saved assistant can carry a caller-authored subsystem server: if `assistantOverrides` replaces the voice, the tools, or the knowledge base, those replacements are caller-authored even though the assistant itself is saved. Give any subsystem server you supply in a request its own authentication in that block's `headers`. + + #### Confirming a withheld send in your logs When Vapi withholds credentials, it also writes an entry to the call's logs: diff --git a/fern/squads/handoff/destinations.mdx b/fern/squads/handoff/destinations.mdx index b647c1f29..51530e743 100644 --- a/fern/squads/handoff/destinations.mdx +++ b/fern/squads/handoff/destinations.mdx @@ -149,6 +149,10 @@ Best for Anthropic models -- single tool with multiple destination options: The destination is determined at runtime via the `handoff-destination-request` webhook: + +A destination's `server` block follows the assistant carrying it. On a saved assistant it keeps your org's webhook credentials; supplied through a transient assistant or `assistantOverrides` it is caller-authored, and Vapi sends to it without them. Give it its own authentication in `server.headers`, as the examples below do. See [Server authentication](/server-url/server-authentication#subsystem-server-urls). + + ```json { "tools": [ @@ -219,7 +223,10 @@ Pass additional context to your webhook for intelligent routing: { "type": "dynamic", "server": { - "url": "https://api.example.com/intelligent-routing" + "url": "https://api.example.com/intelligent-routing", + "headers": { + "Authorization": "Bearer YOUR_API_KEY" + } } } ], From 5603acce45aef87812ac5e9d1305f4e3a75c8c4f Mon Sep 17 00:00:00 2001 From: Jeff Hoekman Date: Wed, 16 Sep 2026 15:42:06 -0400 Subject: [PATCH 2/3] VAP15-54 docs: drop the spaced hyphen in the subsystem intro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same ยง9 fix Steve applied across VAP15-53: a spaced hyphen standing in for a dash becomes a period, with the colon left to introduce the table. Also fixes the number disagreement the dash was hiding - "a tool, a knowledge base ... each carry" now reads as plurals throughout. Refs VAP15-54 Co-Authored-By: Claude Opus 5 --- fern/server-url/server-authentication.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fern/server-url/server-authentication.mdx b/fern/server-url/server-authentication.mdx index fcdd34804..c2929de3a 100644 --- a/fern/server-url/server-authentication.mdx +++ b/fern/server-url/server-authentication.mdx @@ -407,7 +407,7 @@ For webhooks sent to a server URL supplied in the API request, Vapi also strips #### Subsystem server URLs -Some webhooks go to a URL owned by a subsystem rather than to your assistant's webhook URL - a tool, a knowledge base, a custom voice, a dynamic handoff or transfer destination, or a custom endpointing provider each carry their own `server` block: +Some webhooks go to a URL owned by a subsystem rather than to your assistant's webhook URL. Tools, knowledge bases, custom voices, dynamic handoff and transfer destinations, and custom endpointing providers each carry their own `server` block: | Webhook | Server it is sent to | | --- | --- | From 019d656f786568a9b425fbabe6dcd0b7f412169a Mon Sep 17 00:00:00 2001 From: Jeff Hoekman Date: Thu, 12 Nov 2026 10:40:39 -0500 Subject: [PATCH 3/3] VAP15-54 docs: align subsystem credentials with the VAPSEC-627 stack Withholding is decided per assistant: one caller-supplied server in assistantOverrides withholds the org credential from every subsystem request on that assistant, saved tools included. Add apiRequest and MCP requests, list what is withheld, document the second log entry, and rename the handoff example token placeholder. Co-Authored-By: Claude Opus 5.5 --- fern/server-url/server-authentication.mdx | 36 +++++++++++++++-------- fern/squads/handoff/destinations.mdx | 6 ++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/fern/server-url/server-authentication.mdx b/fern/server-url/server-authentication.mdx index c2929de3a..8e75ddd02 100644 --- a/fern/server-url/server-authentication.mdx +++ b/fern/server-url/server-authentication.mdx @@ -407,21 +407,27 @@ For webhooks sent to a server URL supplied in the API request, Vapi also strips #### Subsystem server URLs -Some webhooks go to a URL owned by a subsystem rather than to your assistant's webhook URL. Tools, knowledge bases, custom voices, dynamic handoff and transfer destinations, and custom endpointing providers each carry their own `server` block: +Tools, knowledge bases, custom voices, handoff and transfer destinations, and custom endpointing providers each carry their own `server` block. These requests go to that block's URL, not to your assistant's webhook URL: -| Webhook | Server it is sent to | -| --- | --- | -| `tool-calls`, `function-call` | the tool's | -| `knowledge-base-request` | the knowledge base's | -| `voice-request` | the custom voice's | -| `handoff-destination-request` | the handoff destination's | -| `transfer-destination-request` | the transfer destination's | -| `call.endpointing.request` | the endpointing provider's | +- `tool-calls` and `function-call`, to the tool's server +- `knowledge-base-request`, to the knowledge base's server +- `voice-request`, to the custom voice's server +- `handoff-destination-request` and `transfer-destination-request`, to the destination's server +- `call.endpointing.request`, to the endpointing provider's server +- `apiRequest` tool requests, to the tool's URL +- MCP requests, to the MCP server's URL, both when Vapi lists its tools at call start and on each tool call -**These follow the assistant that carried them.** A tool on a saved assistant is org-configured and keeps your credentials. The same tool on a transient assistant or in `assistantOverrides` is caller-authored, and Vapi sends to its URL without them. +When a transient assistant or `assistantOverrides` supplies a server URL or `credentialId` for any of these, Vapi withholds your org credential from **every** request in this list whose URL differs from your assistant's webhook URL. On a withheld request: + +- your org's `secret`, `headers`, and credential are not sent +- a `credentialId` in the request is ignored, because it points at a credential stored in your org +- the `secret` and `headers` you set in that `server` block are still sent +- credential fields in the payload arrive as `"[REDACTED]"` + +Overrides that set only `variableValues` don't trigger this. In chats, the setting lasts for the whole session, including resumed sessions and handoffs. -A saved assistant can carry a caller-authored subsystem server: if `assistantOverrides` replaces the voice, the tools, or the knowledge base, those replacements are caller-authored even though the assistant itself is saved. Give any subsystem server you supply in a request its own authentication in that block's `headers`. +Supplying one tool, voice, or knowledge base through `assistantOverrides` also withholds your org credential from the saved assistant's own tools, voice, and knowledge base. Put authentication for those endpoints in their `server.headers`, or move the override onto a saved assistant. #### Confirming a withheld send in your logs @@ -432,7 +438,13 @@ When Vapi withholds credentials, it also writes an entry to the call's logs: Assistant credentials withheld from : this server url came from an assistant this request supplied, not from your org configuration ``` -Open the call in [**Calls**](/observability/logs/call-logs) under **Logs**. On its **Logs** tab, search for `credentials withheld`. Leave the category filter unset: the entry is not always filed under **Webhook**. +For a tool, custom voice, or MCP request withheld under [Subsystem server URLs](#subsystem-server-urls), the entry reads: + +```text +Assistant credentials withheld from : this assistant carries server configuration this request supplied +``` + +Open the call in [**Calls**](/observability/logs/call-logs) under **Logs**. On its **Logs** tab, search for `credentials withheld`, which matches both entries. Leave the category filter unset: the entry is not always filed under **Webhook**. Vapi writes the entry **on each send** of a message type that is not suppressed. The same entry can repeat within a call. Vapi suppresses it for high-volume message types: `model-output`, `speech-update`, `status-update`, `transcript`, `transcript[transcriptType="final"]`, `voice-input`, and `voice-request`. diff --git a/fern/squads/handoff/destinations.mdx b/fern/squads/handoff/destinations.mdx index 51530e743..9b6102ea0 100644 --- a/fern/squads/handoff/destinations.mdx +++ b/fern/squads/handoff/destinations.mdx @@ -150,7 +150,7 @@ Best for Anthropic models -- single tool with multiple destination options: The destination is determined at runtime via the `handoff-destination-request` webhook: -A destination's `server` block follows the assistant carrying it. On a saved assistant it keeps your org's webhook credentials; supplied through a transient assistant or `assistantOverrides` it is caller-authored, and Vapi sends to it without them. Give it its own authentication in `server.headers`, as the examples below do. See [Server authentication](/server-url/server-authentication#subsystem-server-urls). +If a transient assistant or `assistantOverrides` supplies any tool, voice, or destination server, Vapi sends `handoff-destination-request` without your org credential. Put authentication in `server.headers`, as these examples do; see [Subsystem server URLs](/server-url/server-authentication#subsystem-server-urls). ```json @@ -164,7 +164,7 @@ A destination's `server` block follows the assistant carrying it. On a saved ass "server": { "url": "https://api.example.com/determine-handoff-destination", "headers": { - "Authorization": "Bearer YOUR_API_KEY" + "Authorization": "Bearer YOUR_WEBHOOK_TOKEN" } } } @@ -225,7 +225,7 @@ Pass additional context to your webhook for intelligent routing: "server": { "url": "https://api.example.com/intelligent-routing", "headers": { - "Authorization": "Bearer YOUR_API_KEY" + "Authorization": "Bearer YOUR_WEBHOOK_TOKEN" } } }