ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Developer cookbook

Per-call variables

Personalize a saved agent for each call without changing its deployed prompt, tools, or settings.

Put placeholders in your saved agent's prompt, then supply a variables object when starting a call. The saved configuration and version history stay unchanged. ThunderPhone renders the text before sending the call configuration to the voice runtime.

When no values are needed, omit variables, do not send null (rejected with 400).

Placeholders and defaults

You are calling {{name|Friend}} about account {{account_id}}.
The available appointment is {{ appointment_slot }}.

Names are case-sensitive and follow [A-Za-z_][A-Za-z0-9_]*. Whitespace around the name is allowed; whitespace after | is part of the default and is preserved. {{name|Friend}} uses Friend when name is missing or null; an empty string is an explicitly supplied value. Missing values without a default become empty strings and their names appear in unresolved_variables. Text between double braces that is not a valid placeholder is removed. Double-braced text inside each supplied value is removed independently; a value cannot remove surrounding prompt text or another value. Unmatched double-brace delimiters are removed too. JSON examples in prompts must not use {{. Values are plain text, never evaluated as code or recursively expanded as templates.

Variables can also appear in acknowledgement prompts, outbound voicemail messages, and the consent announcement text when that field is sent for a phone call. The agent has no separate first_message field: put its opening instructions in the prompt. Existing voicemail {agent_name} and {org_name} placeholders continue to work.

Values may be strings, numbers, booleans, or null; booleans render as true and false. Unicode control (Cc) characters except newline (\n), tab (\t), and carriage return (\r), all format (Cf) characters, and surrogate (Cs) code points are removed; \r\n is normalized to \n. Each value is limited to 2,000 characters when rendered. Supplied strings are cleaned and truncated before storage as well. The original object must fit in 32 KB of UTF-8 JSON; larger objects receive 400 on call/session requests, while campaign imports report invalid rows individually. Arrays and nested objects are not accepted as values. Nonmatching metadata keys (for example a CSV header with a space) are retained and echoed but cannot be referenced by a placeholder.

Where values come from

Outbound API

Send variables alongside agent_id on POST /v1/call:

{
  "from_number": "+15551234567",
  "to_number": "+14155550199",
  "agent_id": 12,
  "variables": {
    "name": "Ada",
    "account_id": "A-17",
    "appointment_slot": "Tuesday at 10 AM"
  }
}

It also works with the phone number's default outbound agent, or with an inline config.prompt. An idempotency key cannot be reused with different variables.

Campaign CSV

Non-phone CSV columns are already stored as contact variables. Each dial now uses them automatically. Use headers such as name, account_id, and appointment_slot to match your placeholders. The existing name mapping can combine first and last name columns into the variable name.

Dynamic configuration webhook

On the blocking configuration webhook path, return a saved agent in your organization plus any per-call values:

{"agent_id": 12, "variables": {"name": "Ada", "account_id": "A-17"}}

The response's keys overwrite request-level variables, while other request keys remain. A response value of null selects the placeholder's default. The merged object must also fit in 32 KB. Saved-agent responses accept only agent_id and variables; return an inline configuration when you need to replace the prompt or settings. A response containing prompt always uses inline configuration: any agent_id in that response is ignored, including null or non-integer metadata. The inline prompt still must pass normal validation. Inline webhook responses can include variables too. Saved-agent webhook responses use the agent's deployed A/B split on both phone and widget calls; variables render after variant selection. On inbound phone calls, use a number with no assigned inbound agent and configure its phone-number or organization webhook; widget keys use mode="webhook". Endpoint-system incoming notifications do not supply blocking configuration responses.

Widget and Realtime session APIs

POST /v1/widget/session accepts a top-level variables object. Its publishable key selects the saved agent. Webhook-mode keys forward these values to the configuration webhook and merge the response as described above. Browser-supplied widget/realtime variables are client-controlled, forwarded verbatim in web.incoming after the validation and string cleanup described above, and echoed into completion webhooks and call history. Do not treat them as trusted identity or authorization data.

POST /v1/realtime/sessions accepts variables alongside agent_id (or inline config). These are session-create API fields. The Realtime WebSocket bridge does not forward a variables option; supply it directly to the session-create API. Widget clients must include variables in the posted session payload; SDK forwarding is not part of this API change. Builder mic and simulated test calls resolve defaults and missing placeholders but have no per-call variables input.

Values returned after the call

GET /v1/calls, GET /v1/calls/{call_id}, telephony.complete, and web.complete include the final merged variables and unresolved_variables. Legacy completion payloads that include data.history include them too:

{
  "variables": {"name": "Ada", "account_id": "A-17"},
  "unresolved_variables": ["appointment_slot"]
}

Store your CRM or task identifier in the variables object to join the completed call back to its source record. These fields are retained with the call record; only send information appropriate to retain in call history and webhooks.

Existing prompt compatibility

Rendering also applies to existing saved-agent and A/B variant prompts, inline outbound and realtime configurations, and prompts returned by configuration webhooks. Unknown {{name}} placeholders become blank text, even when no variables are supplied. Check existing prompts before rollout, including externally supplied inline/webhook prompts that ThunderPhone cannot inventory. Builder mic and simulation calls apply the same default/blank behavior.