How it works
- Store a key. Generate a 256-bit key and save it as an integration secret.
- Return a ciphertext. The dynamic variables webhook returns the caller’s credential in a new
encrypted_dynamic_variablessection, encrypted with that key. - Reference it. The assistant configuration points at the variable and the key with
{{variable | encryption_secret_ref}}. - Telnyx decrypts it only where a credential is actually needed, for that conversation only.
1. Store the encryption key
Generate 32 random bytes and store them base64url-encoded as an integration secret. The secret’s identifier is what the configuration references.POST /integration_secrets. The identifier is what the configuration references:
= is accepted.
Keep the raw key: the webhook needs it to encrypt.
2. Return encrypted variables from the webhook
The dynamic variables webhook response gains an optionalencrypted_dynamic_variables section, a sibling of dynamic_variables:
The two sections are separate namespaces. An encrypted variable is never substituted into instructions, greetings, messages, or tool descriptions — only into the credential positions in step 3. A plain dynamic variable is never usable as a credential. Using the same name in both sections is not an error, but it is almost always a mistake, and Telnyx flags it as one.
Encryption scheme
AES-256-GCM, nonce-prefixed, base64url-encoded:- Nonce: 12 random bytes, unique per encryption, prefixed to the ciphertext.
- Tag: the standard 16-byte GCM tag, appended by the cipher.
- Plaintext: an opaque UTF-8 string, at most 8 KB. No associated data.
There is no
openssl enc equivalent: that command does not support GCM.Key rotation
Update the integration secret’s value. Ciphertexts produced with the old key fail authentication — and the affected requests fail safely, per failure behavior — until the webhook encrypts with the new one. Rotate the key and the webhook together.3. Reference the credential
Reference an encrypted variable with pipe syntax, naming the variable and the secret holding its decryption key. Whitespace around the parts and the pipe is optional.MCP server credential
The wholeapi_key_ref value is one reference:
mcp_token becomes that conversation’s bearer token for the server. A plain identifier in api_key_ref keeps its existing meaning — one static integration secret for every conversation. The two forms are mutually exclusive per server.
Tool webhook header values
As a token inside a configured header value, alongside the existing{{#integration_secret}} and {{plain_variable}} syntax:
Everywhere else
Anywhere else — instructions, greetings, messages, tool descriptions, webhook URLs, preset body fields, preset query parameters — the reference is not resolved. Where Telnyx can tell at save time that a reference sits in a position that cannot resolve one, the configuration is rejected with a422.
Reading a configuration back always returns the reference verbatim. A decrypted value is never echoed on a configuration endpoint.
When credentials refresh
Each webhook response replaces the stored set for that conversation in full, so a token refreshed on one turn is the token used on the next. A response that omits the section leaves the previous set in place; a response with an empty section clears it.
Validation
Rejected at save time with a422:
- A value that looks like a reference but does not parse. A typo is never quietly downgraded to a plain variable, because that would send the literal
{{…}}as a credential. - An
encryption_secret_refthat names no integration secret on the account. - A reference in a position that cannot resolve one.
Failure behavior
A credential that cannot be resolved at conversation time — the variable is missing from that conversation’s set, the key secret is unavailable, or decryption fails — is never worked around:
There is no fallback to another credential, and the literal
{{…}} is never sent. The failure is recorded with the variable name, the secret reference, and what went wrong — never with key material, ciphertext, or any part of the plaintext.
Confidentiality
- Values are stored and logged only as ciphertext.
- A decrypted value exists in memory only, at the moment it authenticates a request. It is never written to a log, a conversation record, or a webhook log.
- Encrypted variables cannot reach model-visible text: the templating that renders instructions, greetings, and tool descriptions does not resolve the pipe form at all.
- Decrypted values are never returned by a configuration endpoint.
End-to-end example
-
Store
base64url(32 random bytes)as integration secretmcp_enc_key. -
Configure the MCP server with
"api_key_ref": "{{mcp_token | mcp_enc_key}}". -
Point the assistant’s
dynamic_variables_webhook_urlat the endpoint. -
On each webhook call, identify the caller from the payload, fetch that user’s token, encrypt it, and return it:
Authorization: Bearer <that user's token>. Two concurrent conversations for two different callers use two different tokens, and neither can reach the other’s data.
Related resources
- Dynamic Variables - The webhook this section extends, and the plain-variable syntax.
- Integrations - Store the encryption key as an integration secret.
- Preset Webhook Parameters - Fixed tool values the model never sees.
- Voice AI Assistant API Reference - Assistant, MCP server, and tool configuration.