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

# Custom variables

> Define per-assistant variables and inject live values — from the API, campaign leads, an inbound webhook, or system context — into prompts, greetings, and tools

Custom variables let you write an assistant once and personalize every call. Instead of hard-coding a name, an appointment, or an account number into the system prompt, you reference a placeholder like `{{customer_name}}` and supply the value per call — from your API request, a campaign lead, an inbound enrichment webhook, or the platform's built-in system context.

## Reference syntax

Reference a variable with double braces — the preferred, JSON-safe form:

```text theme={null}
Hi {{customer_name}}, I see your appointment is on {{appointment_date}}.
```

The legacy single-brace form `{customer_name}` is also resolved, but **only for keys that are actually known** (a defined or system variable). This keeps literal braces — for example JSON in a tool body — intact. Any placeholder whose key is unknown is left untouched.

## Defining variables on an assistant

Each assistant carries a list of variable definitions. A definition has:

| Field           | Required | Description                                                                                                                                                                                                                             |
| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`           | yes      | The identifier used as `{{key}}`. Lowercase letter first, then lowercase letters, digits, and underscores; 1–64 characters (`^[a-z][a-z0-9_]{0,63}$`). Unique per assistant. Cannot be a reserved [system variable](#system-variables). |
| `label`         | yes      | Human-readable name shown in the editor.                                                                                                                                                                                                |
| `description`   | no       | Note on what the variable is for.                                                                                                                                                                                                       |
| `default_value` | no       | Fallback used when no value is supplied at call time.                                                                                                                                                                                   |
| `example`       | no       | Sample value (editor/docs only, never sent).                                                                                                                                                                                            |
| `source`        | no       | Informational hint: `manual` (default), `lead`, `webhook`, or `system`. Drives UI hints and webhook mapping; it does not restrict where a value can come from.                                                                          |

<Note>
  Keys are validated on save: invalid format, a collision with a reserved system variable, a duplicate key, or a missing label are all rejected.
</Note>

## Where variables are substituted

Values are substituted at call start, before the model or flow runs, in these fields:

* Assistant **system prompt**
* Assistant **first message** (greeting)
* Flow node **`start.greeting`**
* Flow node **`agent.instructions`**
* Flow **tool node** request **URL** and header **values**

So a tool node can call `https://app.famulor.de/api/user/orders/{{order_id}}` or send `Authorization: Bearer {{api_token}}` with per-call values.

## Value sources & precedence

A value can arrive from several places. At call start the worker resolves each key with this precedence, highest first:

1. **Explicit** — values passed with the call: API `make-call` `variables`, or a campaign lead's custom fields mapped onto matching keys.
2. **Inbound variable-webhook** — enrichment fetched at call start (see [below](#inbound-variable-webhook)).
3. **System variables** — filled by the platform from call context.
4. **Default** — the definition's `default_value`.

A placeholder with no value at any level is left as-is.

### Explicit values via the API

```bash theme={null}
curl -X POST https://app.famulor.de/api/user/make-call \
  -H "Authorization: Bearer fam_..." \
  -H "Content-Type: application/json" \
  -d '{
    "assistant_id": "asst_123",
    "to_number": "+493012345678",
    "variables": { "customer_name": "Jordan", "appointment_date": "2026-07-10" }
  }'
```

### Campaign leads → variables

In a [campaign](/campaigns/overview), each lead carries free-form **custom fields** (`leads.custom_fields`). At dial time, a lead's custom field is mapped onto a variable with the **same key**. So a CSV column becomes a variable:

```csv theme={null}
phone_number,name,customer_name,appointment_date
+493012345678,Jordan,Jordan,2026-07-10
+491701234567,Alex,Alex,2026-07-11
```

Here the `customer_name` and `appointment_date` columns populate `{{customer_name}}` and `{{appointment_date}}` for each call. Give a lead-sourced variable `source: "lead"` to document the intent.

## System variables

These keys are always available and filled by the worker at call start. They are reserved — you cannot define a custom variable with one of these keys.

| Key              | Label          | Description                                                                             | Example                |
| ---------------- | -------------- | --------------------------------------------------------------------------------------- | ---------------------- |
| `caller_number`  | Caller number  | The phone number the call is coming from (inbound) / being placed to (outbound), E.164. | `+493012345678`        |
| `called_number`  | Called number  | The number that was dialed / your number that received the call, E.164.                 | `+498998765432`        |
| `assistant_name` | Assistant name | The name of the assistant handling the call.                                            | `Reception Bot`        |
| `direction`      | Call direction | `inbound`, `outbound` or `web`.                                                         | `inbound`              |
| `call_id`        | Call ID        | Unique identifier of this call.                                                         | `c_a1b2c3`             |
| `date`           | Date           | Current date at call start (assistant timezone), `YYYY-MM-DD`.                          | `2026-07-05`           |
| `time`           | Time           | Current time at call start (assistant timezone), `HH:MM`.                               | `14:30`                |
| `datetime`       | Date & time    | Current date and time at call start (ISO 8601).                                         | `2026-07-05T14:30:00Z` |
| `weekday`        | Weekday        | Current weekday at call start.                                                          | `Sunday`               |

## Inbound variable-webhook

For **inbound** calls you often don't know the caller in advance. Configure a **variable-webhook** on the assistant (`variable_webhook_url` + `variable_webhook_secret`) and the worker calls it at call start to enrich variables — for example, looking up a customer by their caller number.

### Request

The worker sends a `POST` with a JSON body:

```json theme={null}
{
  "event": "call.variables",
  "assistant_id": "asst_123",
  "call_id": "c_a1b2c3",
  "direction": "inbound",
  "from_number": "+493012345678",
  "to_number": "+498998765432"
}
```

The raw request body is signed with HMAC-SHA256 using the assistant's `variable_webhook_secret`, sent in the header:

```text theme={null}
X-Famulor-Signature: sha256=<hexdigest>
```

### Response

Return the variables to merge:

```json theme={null}
{
  "variables": {
    "customer_name": "Jordan",
    "open_amount": "128.50"
  }
}
```

These values merge **over** system variables and defaults, but **under** any explicit dispatch values. The call is timed out at \~5s; a failure is non-fatal — the worker logs it and continues with the values it already has.

### Native automation (alternative)

Instead of a custom `variable_webhook_url`, you can create an **Automation** with trigger **Inject input variables** (`call.variables`) bound to the assistant. At call start the worker runs that automation synchronously and expects a **Return variables** action (same `{ variables: {…} }` shape). If no matching active automation exists, the classic webhook URL is used as fallback.

### Verifying the signature

<CodeGroup>
  ```js Node.js theme={null}
  import crypto from "node:crypto";

  // rawBody: the exact bytes received, before JSON.parse
  function verify(rawBody, signatureHeader, secret) {
    const expected =
      "sha256=" +
      crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
    const a = Buffer.from(signatureHeader);
    const b = Buffer.from(expected);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  }
  ```

  ```python Python theme={null}
  import hmac, hashlib

  # raw_body: the exact bytes received, before json.loads
  def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
      expected = "sha256=" + hmac.new(
          secret.encode(), raw_body, hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(expected, signature_header)
  ```
</CodeGroup>

<Warning>
  Always compute the HMAC over the **raw** request body bytes, not over a re-serialized object — re-serialization can change whitespace or key order and break the signature. Use a constant-time comparison.
</Warning>

### Example request

```bash theme={null}
curl -X POST https://your-app.example.com/famulor/variables \
  -H "Content-Type: application/json" \
  -H "X-Famulor-Signature: sha256=6d3a...e1f0" \
  -d '{
    "event": "call.variables",
    "assistant_id": "asst_123",
    "call_id": "c_a1b2c3",
    "direction": "inbound",
    "from_number": "+493012345678",
    "to_number": "+498998765432"
  }'
```

## API & MCP

* `GET /v1/assistants/{id}/variables` — read the assistant's variable definitions; scope `assistants:read`.
* `PATCH /v1/assistants/{id}/variables` — replace the variable definitions; scope `assistants:write`.
* MCP tools: `get_assistant_variables`, `set_assistant_variables`.

<Tip>
  Full REST reference lives at [docs.famulor.io](https://docs.famulor.io). Use `{{key}}` everywhere you want a per-call value, keep keys `snake_case`, and give every variable a sensible `default_value` so calls degrade gracefully when a source is missing.
</Tip>
