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

Developer cookbook

Per-call variables and context

Give each call its own details, as named variables for the prompt or a free-form brief, without changing the agent.

Each call can carry two kinds of per-call input. Use either one, or both together:

  • variables: named values that fill {{placeholders}} in the prompt. Use them for fields the prompt refers to by name, such as the caller's name, an account number or an appointment slot.
  • context: free-form text added after the prompt. Use it for a brief that does not fit a template, such as a description of the task for this call or notes collected earlier.

Both work with a saved agent and with an inline configuration, on every way of starting a call described below. Neither changes the saved agent, its version history or its other settings.

{
  "agent_id": 12,
  "variables": {"name": "Ada", "appointment_slot": "Tuesday at 10 AM"},
  "context": "Ada rescheduled twice last month. She prefers mornings and asked us not to call her work number."
}

Variables

Put placeholders in your saved agent's prompt, then supply a variables object when starting a call. 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 and limits

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), format (Cf) characters except the zero-width non-joiner and joiner (U+200C, U+200D, which spell words in Indic and Arabic-script languages), 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.

Context

context is a string of up to 20,000 characters. ThunderPhone adds it after the prompt, once the prompt's variables are filled in, as a delimited block:

<your prompt, with its variables filled in>
 
Details for this call:
<call_context>
Ada rescheduled twice last month. She prefers mornings and asked us not to call her work number.
</call_context>

The agent reads it together with its instructions, so write it the way you would brief a colleague before the call. Context is not a template: {{name}} inside it stays as written. If the text needs a value, put the value in yourself before sending.

  • null, an empty string, or only whitespace means no context.
  • A value that is not a string, or is longer than 20,000 characters, is rejected with 400 on call and session requests; from a configuration webhook it fails the exchange like invalid variables (see below).
  • Leading and trailing whitespace is trimmed, and the same control and format characters as in variable values are removed.
  • Context is part of the call's prompt, so it counts toward the call's prompt size.

On a steps agent, the context follows the prompt of every agent in the call, not each step's instructions.

Where values come from

Outbound API

Send variables and context on POST /v1/call:

{
  "from_number": "+15551234567",
  "to_number": "+14155552671",
  "agent_id": 12,
  "variables": {
    "name": "Ada",
    "account_id": "A-17",
    "appointment_slot": "Tuesday at 10 AM"
  },
  "context": "Ada asked to be called back about the late fee on her March invoice."
}

Both also work with the phone number's default outbound agent, or with an inline config.prompt. With an inline config, context can also go inside config, the same field a configuration webhook returns; if you send both, they must be the same text. An idempotency key cannot be reused with different variables or a different context.

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. Campaign contacts have no per-contact context.

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"},
  "context": "Ada called yesterday about a leak in unit 4B; a plumber is booked for Friday."
}

The response's variables 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, variables, context and a per-call timezone; return an inline configuration when you need to replace the prompt or other 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 and context too. A context that is not a string or is too long fails the exchange: a phone call is rejected, and a widget session request fails with 400. 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.

Agent call setup webhook

To keep a number assigned to its agent and fill in only the call's values, give the agent a call setup webhook. Before each inbound call the agent answers on a number, the webhook gets the call and the agent's id, name and version, and answers with the same variables and context as above, without needing an agent_id. An agent whose settings use a placeholder with no default needs this webhook to answer a phone number: the call does not start when the webhook fails. Placeholders with a default, like {{name|there}}, let the agent answer without it.

SIP headers

Calls that reach an agent over SIP (a SIP address, or a number on a manual SIP connection) can carry context in X-* and User-to-User headers. Each one becomes a sip_ variable, such as {{sip_x_amzn_connectcontactid}} or {{sip_uui}}, with the lowest precedence: a value of the same name from the API or a webhook wins. See Pass call context in SIP headers. Header values are set by the sender, so treat them as untrusted, like any browser-supplied value.

Widget session API

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 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.

The widget's own browser context field is different: it carries text from the page the visitor is on, which the agent treats as website content (see Embed the web widget). A widget call's per-call context comes from a webhook-mode key's configuration webhook, as above. When a call has both, the per-call context comes first, then the page content.

Realtime session API

POST /v1/realtime/sessions accepts top-level variables and context alongside agent_id or an inline config:

{
  "agent_id": 12,
  "variables": {"name": "Ada"},
  "context": "Ada is calling from the front desk about a guest's lost key card."
}

context goes at the top level only; an inline config rejects it as an unknown field.

Realtime WebSocket

With a saved agent, the saved agent's deployed prompt stays its prompt. session.instructions becomes the call's context, and session.prompt.variables (or session.config.variables) become its variables. Choose the agent with session.prompt.id in the first session.update, which starts the session once that update arrives:

{
  "type": "session.update",
  "session": {
    "type": "realtime",
    "prompt": {"id": "12", "variables": {"name": "Ada"}},
    "instructions": "Ada asked for a callback about her delivery window."
  }
}

?agent_id=12 on the connection URL starts the agent as soon as the socket connects, before any session.update, so it carries no per-call input. Add &wait_for_session_update=1 to wait for the first session.update and use its instructions and variables.

In an inline session (no saved agent), session.instructions is the prompt itself, as before. Builder mic and simulated test calls resolve defaults and missing placeholders but have no per-call variables or context input.

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"]
}

The call's prompt_snapshot (GET /v1/calls/{call_id}) shows the agent's prompt with its variables filled in and the context block added.

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

Replays of a call, in validation sets, replay runs and investigations, use that call's variables and context with the prompt under test, so a replay tests what the agent on that call would have been told.

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.