> ## Documentation Index
> Fetch the complete documentation index at: https://cntillion.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Personalization

> Use one agent for inbound and outbound calls with dynamic variables, per-conversation overrides and a caller lookup before the conversation starts.

One agent can answer incoming calls and place outgoing calls. There is no
direction setting and no second agent to keep in sync. The system prompt and
first message stay the same, and each conversation fills them with its own
values.

## Choose the right option

| Option | Use it for | Where to configure it |
| - | - | - |
| Dynamic variables | Names, account context or other values inside the same prompt, first message and tool inputs | Type `{{` in **System prompt** or **First message**, then supply values when the conversation starts |
| Conversation overrides | Replacing a whole prompt, first message, language, workflow start step or supported voice setting for one conversation | Allow each field in agent **Settings → Security**, then supply the override |
| Conversation initiation client data webhook | Looking up a caller before the first workflow step or first message | Workspace **Settings → Webhooks** for the default; agent **Settings → Security** to turn it on or override the endpoint |
| Test values | Trying variables in Preview without contacting the live webhook | **Vars** in the top bar, or in Preview |

## One agent for inbound and outbound calls

`{{system.direction}}` reads `inbound` when someone called you and `outbound`
when the agent placed the call. Write both cases into the system prompt:

```text theme={null}
This call is {{system.direction}}.

If it is inbound, the person called us. Ask how you can help.
If it is outbound, we called about {{reason}}. Say so in your first sentence.
```

A batch fills `{{reason}}` from a column in its recipient list. An inbound call
can leave it empty, because that line doesn't apply to it. Workflow conditions
read `system.direction` too, so a workflow can send each direction down its own
path.

The first message is one field for both directions. Choose whichever fits:

* Write an opening that suits both, such as `Hi, this is Maya from Centillion Labs.`
* Leave **First message** blank. The agent waits for the person to speak. On a
  call the agent placed, it opens the call itself if the person answers and
  stays silent for three seconds.
* Add a `first_message` column to a batch to replace the opening on those calls.
  Inbound calls keep the agent's **First message**.
* Have the initiation webhook return `first_message` to replace the opening on
  one inbound call.

The opening is selected in this order:

1. A recipient's `first_message` in a batch.
2. A literal opening message from the active workflow.
3. A permitted initiation first-message override for this conversation.
4. The **First message** set for the call's language under **Language presets**.
5. The agent's **First message**.
6. Nothing. The agent waits for the person to speak.

An initiation override of `first_message: ""` keeps that conversation silent
when there is no workflow opening, even if the agent has a **First message**.
Batch first-message overrides must contain text.

Whatever opens the call, you are responsible for the notices it has to give.
See [Disclosure requirements](https://cntillion.com/docs/agents/disclosure-requirements.md).

### Give inbound callers their own start

An agent built for outbound calls usually opens with a step that explains why
it called. A caller who rang in needs a different greeting, then the same
conversation. Keep one agent and let the initiation webhook choose the step an
inbound call starts on:

1. **Point a number at the agent.** In **Channels → Phone**, assign a number to
   this agent. A number answers for one agent, so reassign it or import another.
2. **Add the inbound step.** In **Workflow**, add a step for callers, for
   example "Inbound greeting", with the same exits as the outbound greeting and
   wording such as "thanks for calling back" instead of "I'm calling about
   your form". Copy its **Step ID** from the bottom of the step panel, then
   publish: live calls read the published workflow.
3. **Allow the overrides.** On the **Main** branch, open **Settings → Security**
   and switch on **First message** and **Workflow start node** under
   **Overrides**. Then switch on **Fetch initiation client data from a
   webhook**.
4. **Set the webhook.** Add it in **Workspace settings → Webhooks**, or add an
   override for this agent on the same Security tab. Add a secret header so
   your endpoint can tell the request comes from Centillion Labs.
5. **Build the webhook.** Check `direction` first. For `inbound`, the caller's
   number is in `caller_id`: look the caller up, and if there is no match,
   create a record right away so IDs your tools use are real. Return the
   variables, a greeting and the step:

   ```json theme={null}
   {
     "type": "conversation_initiation_client_data",
     "dynamic_variables": { "lead_id": "L-1042", "lead_first_name": "Sam" },
     "conversation_config_override": {
       "agent": {
         "first_message": "Hi, thanks for calling. This is Maya, a virtual assistant. Am I speaking with {{lead_first_name}}?"
       }
     },
     "starting_workflow_node_id": "inbound-greeting"
   }
   ```

   For an unknown caller, ask for their name in the greeting instead, and give
   the agent a tool that saves it. Never answer with an error: a failed lookup
   stops the call from starting. If something inside your endpoint fails,
   return the unknown-caller response.

   For `outbound`, return the variables without `starting_workflow_node_id` or
   a first message, so the call starts and opens as it always has. On those
   calls `caller_id` is your own number and `called_number` is the person
   called; don't create a record for it.
6. **Make the prompt fit both directions.** Replace a line such as "Outbound
   phone call to a lead" with one that reads `{{system.direction}}`.

A batch call supplies its own recipient data and skips the webhook, so it
starts at the workflow's entry as before. Other outbound calls that run the
agent's workflow do call the webhook, with `direction` set to `outbound` (see
[When it runs](#when-it-runs)), which is why the webhook checks it.

Test with a real call: Preview does not call the webhook. Open the conversation
and its **Workflow** tab. It shows the move from **Start** to the inbound step.
If the conversation started on the outbound greeting instead, the step ID was
not in the published workflow: Centillion Labs starts at the entry rather than
drop the call. A start step the agent has not allowed fails the call instead.

### Test both directions in Preview

Preview runs as an inbound conversation and nothing is dialed. To try the
outbound path, open **Vars** in Preview, set `system.direction` to `outbound` and
start a new conversation. The row is there once the prompt uses the variable.
Fill `reason` and any other variable the same way.

## Dynamic variables

Type `{{` in **System prompt** or **First message** to add a variable. The list
shows the variables this agent already uses, then the built-in values, with a
description and an example for the highlighted one. Keep typing to filter, and
press Enter or Tab to insert. A name that isn't on the list reads **New** and
adds a variable.

```text theme={null}
Help {{customer_name}} with their {{plan_name}} subscription.
There are {{open_requests}} open requests. Priority support: {{priority_support}}.
```

![System prompt editor with the variable list open at the cursor, showing user variables, system variables and a description of the highlighted one](https://mintcdn.com/centillion/n9koGqutqlVBNAYF/images/agents/personalization-01-variable-picker.webp?fit=max\&auto=format\&n=n9koGqutqlVBNAYF\&q=85\&s=b6edf7b575ee33a3ee9057b7e82cb076)

Type \{\{ to pick a variable the agent already uses or a built-in value.

Supply values as strings, finite numbers or booleans. `0`, `false` and an empty
string are values; they do not fall back to test values. Use at most 100
custom variables, with strings up to 12,000 characters. Names use letters,
numbers and underscores, start with a letter or underscore, and contain at most
64 characters. Do not use reserved names such as `system`, `call`, `contact`,
`campaign`, names beginning with `__`, or the `system__` namespace.

Custom initiation variables keep multiline text in prompts. Imported contact,
batch-recipient, tool-result and workflow values are limited to a single line
of 200 characters, including a tool result that replaces an initiation
variable. Substitution happens once: a value containing `{{another_variable}}`
is treated as text, not another lookup.

### Supplying live values

* **Inbound phone calls:** return `dynamic_variables` from the initiation webhook.
* **Outbound batches and number test calls:** supply the recipient's `variables`.
  Each extra column in a batch's recipient list becomes a variable. They use
  their own [batch override permissions](https://cntillion.com/docs/api-reference/batch-calls.md). A test
  call lists each variable the published version uses, starting from **Vars**,
  and sends what you see. After you upload a batch file, a notice names any
  variable it has no column for.
* **Developer API chat:** include `conversation_initiation_client_data` in the
  first request. See the example below.

Website chat and messaging channels have no way to supply values, so a variable
in text those channels use stays as written. Keep such text free of variables,
or use the same words for every conversation.

For an HTTP tool, use a variable in a URL or header, or choose a parameter's
**Dynamic variable** value source. A parameter can read any variable the
conversation holds: a batch column, a system value such as
`system.conversation_id`, or a value an earlier tool saved. A URL variable cannot
change the tool's server. Tool-result assignments can update ordinary variables
during the conversation, including ones the conversation started with, and a
workflow condition reads the new value straight away. They cannot replace system
values or initiation secrets.

A live call can't start on a first message with a missing value. If **First
message** uses a custom variable and the call has no value for it, the call
ends before anyone hears anything, and a website call shows an error. Batch calls
follow the same rule, and a recipient's column counts as a value. So use variables
in **First message** only when every call that speaks it supplies them, for
example from the initiation webhook. A workflow that goes straight
from Start to a Say step replaces the first message, so the first message's
variables are not required.

A prompt is different. A variable with no value stays in the prompt as written
and the call goes on, so a prompt can hold variables that only some calls fill.
Test values are never live defaults.

### Built-in values

These are provided by Centillion Labs and cannot be replaced by the webhook:

| Placeholder | Value |
| - | - |
| `{{system.direction}}` | `inbound` or `outbound` for a phone call |
| `{{system.conversation_id}}` | This conversation's identifier |
| `{{system.date}}` | Today's date, in UTC |
| `{{system.hour}}` | The current hour, from 0 to 23, in UTC |
| `{{system.weekday}}` | The day of the week, from 0 for Sunday to 6, in UTC |
| `{{system.now}}` | The current moment as a UTC timestamp |
| `{{call.lead_number}}` | The other party's number, when known |
| `{{call.our_number}}` | Your phone number, when known |
| `{{contact.name}}` | The saved contact's name, when matched |
| `{{contact.email}}` | The saved contact's email, when matched |
| `{{contact.phone}}` | The saved contact's phone number, when matched |
| `{{campaign.name}}` | The campaign that placed the call |
| `{{system.agent_id}}` | The agent ID on initialized conversations |
| `{{system.user_id}}` | The optional `user_id` returned with initiation data |
| `{{call.carrier_id}}` | The carrier call identifier, when supplied |
| `{{call.sip_id}}` | The SIP call identifier, when supplied |

A built-in value that isn't known on a call becomes empty text.

An inbound call's `caller_id` and an outbound call's `called_number` identify
the other party. A withheld or unavailable number can be `null`.

### Test values

**Vars** in the top bar lists the custom variables this branch uses. Enter a
value and choose **Text**, **Number** or **Boolean**. Values save to the branch's
draft as you type and need no publish. Preview starts each conversation with
them, and a variable with no value stays as written, even in the first message.

**Vars** in Preview, or **Overview** in its **Debugger**, changes the values for
one conversation and leaves the saved ones alone. Preview does not call the
initiation webhook.

Use synthetic test data. Secret variables are excluded from test values.

![Dynamic variables panel open from the top bar, with a name and type for each variable and its test value](https://mintcdn.com/centillion/n9koGqutqlVBNAYF/images/agents/personalization-02-test-values.webp?fit=max\&auto=format\&n=n9koGqutqlVBNAYF\&q=85\&s=68b72d8cc0a83fc35a021e9a7e83659c)

Test values keep their text, number or boolean type and never become live defaults.

## Configure an initiation webhook

### Workspace default

Open **Workspace settings → Webhooks**. Under **Conversation initiation client
data webhook**, click **Add webhook**, enter a public HTTPS URL and add any
request headers, then click **Add webhook** in the panel. This supplies a
default endpoint; it does not turn initiation on for any agent.

**Edit** opens the same panel. **Remove** asks first, because agents that use
the workspace webhook cannot start phone calls until you add another or give
them an override.

![Workspace conversation initiation client data webhook card with a POST endpoint and an Edit button](https://mintcdn.com/centillion/AT99rMc6x8lvrLU3/images/agents/personalization-04-workspace-webhook.webp?fit=max\&auto=format\&n=AT99rMc6x8lvrLU3\&q=85\&s=755628b39d4759875e82fef3fe063f7c)

The workspace default stores a public HTTPS endpoint and encrypted request headers.

### Agent settings

On the agent's **Main** branch, open **Settings → Security**.

**Overrides** lists every part of the configuration a client can replace when
a conversation starts. All start off. Switch on only the ones your endpoint or
client should provide; hover a name to see the field it allows, or see
[Allowed overrides](#allowed-overrides). Dynamic variables need no permission.

**Conversation initiation client data webhook** has one switch, **Fetch
initiation client data from a webhook**:

| Switch | Initiation data webhook override | Behavior |
| - | - | - |
| Off | Any | Do not fetch initiation data for this agent. Developer API chat can still supply explicit data and use the allowed overrides. |
| On | None | Use the workspace webhook. Without one, phone calls to the agent cannot start, and the page warns you. |
| On | Set | Use this agent's URL and headers instead of the workspace webhook. |

To set the override, click **Add webhook** under **Initiation data webhook
override**, enter the URL and headers, and click **Add override**. **Remove
override** goes back to the workspace webhook. Turning the switch off keeps an
override for the next time you turn it on.

Each switch saves as you flip it. These security settings are shared across
the agent's branches and take effect for new conversations without publishing.
Owners and members can manage them. Header values are encrypted and hidden
after saving. Leave a saved value blank to keep it. Changing the endpoint to a
different origin requires entering its headers again; saved credentials are
not forwarded to a different server.

![Agent Security settings with the First message and Workflow start node overrides allowed and the initiation webhook switched on with an agent override](https://mintcdn.com/centillion/AT99rMc6x8lvrLU3/images/agents/personalization-03-agent-webhook.webp?fit=max\&auto=format\&n=AT99rMc6x8lvrLU3\&q=85\&s=f5bb3c8979c675f44a58baf55f870e91)

Each agent allows individual overrides, turns the webhook on and can override its endpoint.

### When it runs

| Conversation | Initiation behavior |
| - | - |
| Inbound phone call | Fetch once before the workflow and first message |
| Outbound call running the agent's workflow, without batch client data | Fetch once before its first configuration is used |
| Outbound batch or phone-number test | Use supplied recipient data; skip the webhook, even if the variables object is empty |
| Other outbound call | Keep the fixed outbound persona; skip the webhook |
| Browser call | Skip the webhook; visitors cannot supply initiation data |
| Developer API chat | Use explicit initiation data on the first request; no automatic webhook lookup |
| Preview | Use test values; skip the webhook |
| Messaging channels | No initiation webhook lookup |

### Request

Centillion Labs sends an HTTPS `POST` with `Content-Type: application/json`
and your configured headers:

```json theme={null}
{
  "agent_id": "agent_123",
  "caller_id": "+12025550123",
  "called_number": "+12025550124",
  "conversation_id": "voice:conversation-room",
  "call_sid": "carrier-call-id",
  "call_id": "sip-call-id",
  "direction": "inbound"
}
```

`call_sid` is an empty string when unavailable. `call_id` is omitted when
unavailable. Use `conversation_id` as an idempotency key in your backend.
No transcript is sent in this request.

### Response

Return a successful HTTP status and this JSON shape:

```json theme={null}
{
  "type": "conversation_initiation_client_data",
  "dynamic_variables": {
    "customer_name": "Jamie",
    "plan_name": "Standard",
    "open_requests": 0,
    "priority_support": false
  },
  "user_id": "customer-123",
  "conversation_config_override": {
    "agent": {
      "first_message": "Welcome back, {{customer_name}}. How can I help?",
      "language": "en"
    }
  },
  "starting_workflow_node_id": "inbound-greeting"
}
```

`dynamic_variables` defaults to an empty object. `user_id`,
`conversation_config_override` and `starting_workflow_node_id` are optional.
`user_id` supplies context; it does not authenticate the caller or change
conversation ownership.

`starting_workflow_node_id` starts the conversation on that workflow step
instead of the entry, as if it had just moved there: the step's own work runs
on the first turn and its exits apply from then on. The conversation's
**Workflow** tab shows the move from **Start**. A step the agent's published
workflow does not have is ignored, and so is the **Start** step itself: the
conversation starts at the entry.

### Allowed overrides

All override permissions start off. Providing a field that is not allowed
fails initialization; it is not silently ignored.

| Security switch | Response field | Accepted value |
| - | - | - |
| Agent language | `conversation_config_override.agent.language` | `en`, `es` or `it` |
| First message | `conversation_config_override.agent.first_message` | Text up to 2,000 characters; an empty string is allowed |
| Workflow start node | `starting_workflow_node_id` | A **Step ID** from the agent's workflow, up to 200 characters |
| System prompt | `conversation_config_override.agent.prompt.prompt` | Non-empty text up to 8,000 characters |
| Voice | `conversation_config_override.tts.voice_id` | An available voice compatible with the agent's configured pipeline and voice service |
| Voice speed | `conversation_config_override.tts.speed` | A number from `0.7` to `1.2`, on a compatible staged voice |
| Voice stability | `conversation_config_override.tts.stability` | A number from `0` to `1`, on a staged voice that supports stability |
| Voice similarity | `conversation_config_override.tts.similarity` | A number from `0` to `1`, on a staged voice that supports similarity |
| Maximum conversation duration | `conversation_config_override.conversation.max_duration_seconds` | `60`–`3600` seconds, in multiples of `60` |

Voice tuning is unavailable in the duplex pipeline and on expressive models
that do not support tuning. Centillion staged voices retain speaking-speed
control when expressive mode is on. An incompatible tuning or voice override
prevents startup. Overrides
cannot change the pipeline, model provider or recording policy. Unsupported
fields are rejected.

The overrides apply only to this conversation. They do not edit the agent or
its published version. A prompt override replaces the agent's base instructions;
workflow steps and specialist instructions still control their own tasks.

### Failures and retries

The endpoint must respond within four seconds after connection resolution.
DNS resolution has a separate three-second limit. Responses must fit within
262,144 bytes. Private network addresses, redirects, non-success status codes,
invalid JSON, unsupported fields and disallowed overrides fail startup.

A successful initialization is reused on retries of the same conversation.
A failed lookup is also remembered, so retrying that conversation does not
repeatedly call your endpoint. Fix the response or settings, then start a new
call. If your lookup finds no customer, return a valid response with safe
values for every required variable to use a generic opening.

## Secret variables

Prefix a credential with `secret__`, for example `secret__account_token`.
It is encrypted for that agent and conversation, and can fill an HTTP or MCP
tool's header:

```text theme={null}
Authorization: Bearer {{secret__account_token}}
```

Secret values are not substituted into prompts, first messages or URLs. The Client
data view shows `[REDACTED]`. Exact copies of the secret in public initiation
text, tool results and tool errors are redacted as well. Do not use these
variables for ordinary customer context or put credentials into a public
variable, tool body, first message or test value.

The initiation endpoint belongs to your workspace. For regulated data, use an
endpoint covered by your organization's agreements and send only the context
the lookup needs.

## Developer API chat

Send initialization data only when starting a new conversation:

```json theme={null}
{
  "message": "Can you check my subscription?",
  "userId": "customer-123",
  "stream": false,
  "conversation_initiation_client_data": {
    "type": "conversation_initiation_client_data",
    "dynamic_variables": {
      "customer_name": "Jamie",
      "plan_name": "Standard"
    }
  }
}
```

Use this body with `POST /api/v2/agents/{agentId}/chat`. Later requests use the
returned `conversationId` and omit initiation data. Retrying the same initial
`requestId` must use the same data. Chat accepts agent overrides; voice and
call-duration overrides require a voice conversation. See the
[developer API](https://cntillion.com/docs/api-reference/developer-api.md) for authentication and responses.

## Initiation and event webhooks

An initiation webhook supplies data **before** a conversation starts, and the
conversation waits for its response. The [event webhooks](https://cntillion.com/docs/developer-guides/webhooks.md)
on the same workspace page report events such as completed calls and analysis.
They use a delivery queue, signing secrets and delivery logs. Configuring one
kind does not configure the other.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.