Custom Actions

Variables and Secrets

Manage reusable values and secure credentials across your custom actions

|View as Markdown
1 min read
ℹ️

Custom actions require a Builder plan or higher.

Upgrade to Builder →

Application Variables

Application variables are reusable values shared across all custom actions in your app.

Creating Variables

  1. Open any custom action
  2. Navigate to Variables tab
  3. Click Add Variable
FieldDescriptionExample
NameVariable identifier: letters, numbers, and underscores, not starting with a number (UPPER_SNAKE_CASE recommended)API_KEY
LabelDisplay nameAPI Key
TypeData typesecret
ValueActual valuesk-abc123...
RequiredMarks a variable that must have a valueOn

Variable Types

TypeUse CaseStorage
stringText values, URLsPlain text
numberNumeric valuesPlain text
booleanTrue/false flagsPlain text
secretAPI keys, passwordsEncrypted
urlBase URLs, endpointsPlain text with validation

Using Variables

Reference variables with {{var.VARIABLE_NAME}} syntax:

bash
https://{{var.API_DOMAIN}}/v{{var.API_VERSION}}/endpoint

Authorization: Bearer {{var.API_KEY}}
X-Workspace-ID: {{var.WORKSPACE_ID}}

{
  "api_key": "{{var.API_KEY}}",
  "environment": "{{var.ENVIRONMENT}}"
}

Variable Resolution

Variables can be used in:

  • Exact match: Entire value is the variable
  • Embedded: Variable within a larger string
bash
{{var.API_KEY}}

Bearer {{var.API_KEY}}
https://{{var.DOMAIN}}/api/v{{var.VERSION}}

System Variables

System variables give your custom actions access to runtime context: information about the current conversation, user, and timing that the platform automatically provides. Unlike application variables (which you define), system variables are built-in and always available.

When to use system variables:

  • Track which user triggered an action for audit logs
  • Add timestamps to records you create via API

Available System Variables

VariableTypeDescription
{{system.user_id}}StringUnique identifier for the current user
{{system.consumer_id}}StringIdentifier of the end user (consumer) chatting with your app
{{system.timestamp}}StringCurrent time as an ISO 8601 string
{{system.session_id}}StringUnique identifier for the current chat session
{{system.chat_transcript_url}}StringLink to this conversation in the Chipp builder
{{system.external_id}}StringOpaque identifier passed via ?chipp_external_id= on the session URL
{{system.query.NAME}}StringValue of the NAME URL query param captured when the session started
{{system.whatsapp_phone}}StringThe WhatsApp user’s phone number, for conversations that arrived over WhatsApp
{{system.whatsapp_profile_name}}StringThe sender’s WhatsApp profile name from Meta. Empty when Meta omits it. User-controlled text

User ID

What it is: A unique identifier for the person chatting with your agent.

Why use it: Track actions per user, create user-specific records, or implement per-user rate limiting in your backend.

Format: String (e.g., "user_abc123" or "clx9f2k...")

Example: Create a user-specific record

json
{
  "user_id": "{{system.user_id}}",
  "action": "submitted_feedback",
  "data": "{{feedbackText}}"
}

Example: Add to request headers for tracking

plaintext
X-Chipp-User-ID: {{system.user_id}}
ℹ️

The user ID is consistent across sessions for the same user, making it useful for building user-specific features in your integrations.


Timestamp

What it is: The current time when the action executes, as an ISO 8601 string in UTC.

Why use it: Add “created at” timestamps to records, implement time-based logic, or track when actions were triggered.

Format: ISO 8601 string (e.g., "2026-04-19T18:40:52.000Z")

Example: Log when a record was created

json
{
  "created_at": "{{system.timestamp}}",
  "user_id": "{{system.user_id}}",
  "note": "{{noteContent}}"
}

Example: Append timestamped data to Google Sheets

json
{
  "values": [[
    "{{system.timestamp}}",
    "{{system.user_id}}",
    "{{eventDescription}}"
  ]]
}
ℹ️

The timestamp is an ISO 8601 string, not a number. If your API expects a numeric Unix timestamp, convert it on your backend.


Session ID

What it is: A unique identifier (UUID) for the current chat conversation.

Why use it: Track specific conversations, create links back to chats in your CRM, or log interactions with session-level granularity.

Format: String (UUID, e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890")

Example: Send session reference to CRM

json
{
  "session_id": "{{system.session_id}}",
  "user_id": "{{system.user_id}}",
  "lead_status": "{{leadStatus}}"
}

Chat Transcript URL

What it is: A link to the current conversation in the Chipp builder.

Why use it: Include in webhook payloads so your team can click from your CRM, Slack, or ticketing system straight into the conversation.

Format: String (e.g., "https://build.chipp.ai/#/app/YOUR_APP_ID/conversations/3ace0bfc-6b75-...")

Example: Send the conversation link to a CRM via webhook

json
{
  "lead_name": "{{leadName}}",
  "transcript_link": "{{system.chat_transcript_url}}",
  "session_id": "{{system.session_id}}"
}
ℹ️

The link opens the conversation in the builder, so only people who can sign in to your Chipp workspace can view it. It does not return JSON. Use it for internal team tools, not for public-facing integrations.


No consumer OAuth tokens

There is no system variable for an end user’s OAuth token. A custom action cannot send the token a user created when they connected a hosted integration (for example their own Google or Salesforce account). To call an API as each end user, use Credential source: Collected from the user each session on the Auth tab, or use a built-in integration that supports per-user accounts.


External ID

What it is: An opaque identifier bound to the chat session. The platform maintains it as a first-class field on the session record (chat.sessions.external_id), so it is reliably available throughout the conversation. You can set it via query param (client-driven) or Builder API (server-driven). Both paths populate the same field.

Why use it: Bind a chat session to a known entity in your system (a patient record, a lead ID, a campaign reference) without requiring account creation. Your backend receives the identifier in the custom action payload and can look up the record it references.

Format: String (colon-free, up to 1024 characters, e.g., "lead_abc123", "patient-987")

How to set it (two sources, same session-level field):

  1. Query param (client-driven). Append ?chipp_external_id=<your-value> to any chat link or embed URL:

    plaintext
    https://your-app.on.chipp.ai?chipp_external_id=patient-987
  2. POST /consumers/auth (server-driven). If you bridge your own backend auth into Chipp via the Builder API, pass external_id in the request body. The value is bound to the auth session minted for that call, and every chat session subsequently created under the returned bearer token (via ChippWidget.setUser({ token })) inherits it automatically, with no query param needed.

    bash
    curl -X POST https://build.chipp.ai/api/v1/apps/YOUR_APP_ID/consumers/auth \
      -H "Authorization: Bearer chipp_YOUR_API_KEY" \
      -d '{"email": "jane@example.com", "external_id": "patient-987"}'

Precedence: If both a query param AND a /consumers/auth external_id are present for the same session, the explicit request-level value (from /consumers/auth) wins over an inbound query-param promotion. Either way, the value lands in the SAME field (chat.sessions.external_id). There is no separate durable “consumer-level” external ID.

Example: Bind a session to a CRM record

json
{
  "external_id": "{{system.external_id}}",
  "session_id": "{{system.session_id}}",
  "event": "chat_completed"
}
⚠️

Promotion rules. Values containing : (colon) are NOT promoted to the first-class external_id field, even if you set them. This guard protects internal channel identifiers (e.g. whatsapp:, voice:) from collision. If your value contains a colon:

  • It will NOT appear in {{system.external_id}}
  • Use {{system.query.chipp_external_id}} to read it instead
  • To use it in custom actions, bind an alternate query param that is colon-free

Values must also be colon-free when passed to /consumers/auth. Invalid values are rejected with a 400 error.


URL Query Parameters

What it is: A sub-namespace that gives custom actions deterministic access to URL query parameters captured when the consumer’s session started. Replace NAME with the query parameter key you want.

Why use it: Pass attribution tokens, variant IDs, or any other value embedded in your link directly into a custom action header or body. The platform resolves the value at request time from the stored session record, so the model never has to copy the raw value from the conversation.

Format: String (resolves to empty string if the session has no such param)

How it works: If a visitor arrives at:

plaintext
https://your-app.on.chipp.ai?rp_token=abc123&ab_variant=B

then {{system.query.rp_token}} resolves to abc123 and {{system.query.ab_variant}} resolves to B in any custom action triggered during that session. The agent still decides when to call the action; the platform guarantees the value.

Example: Pass an attribution token in a header

plaintext
Authorization: Bearer {{system.query.rp_token}}

Example: Forward a variant and session reference to an analytics endpoint

json
{
  "variant": "{{system.query.ab_variant}}",
  "external_id": "{{system.external_id}}",
  "session_id": "{{system.session_id}}"
}

Encoding: Values are URL-encoded when used in the base URL (injection guard), and passed as-is in headers and body fields.

ℹ️

Reserved keys that are never captured as query params: bt, session, share, magic_link, q, widget, chat_session. These are used internally by Chipp and always resolve to an empty string.


WhatsApp Phone

What it is: The phone number of the user chatting with your agent over WhatsApp, sourced from the same canonical channel metadata that Sheets export renders as its own field.

Why use it: Populate a phone field in an outbound webhook (e.g. an N8N or CRM payload) without asking the model to copy the number out of the conversation text.

Format: String, digits only, no + or formatting (e.g. "15551234567")

Example: Send the WhatsApp phone number to N8N

json
{
  "phone": "{{system.whatsapp_phone}}",
  "session_id": "{{system.session_id}}",
  "message": "{{messageText}}"
}
ℹ️

This variable resolves to an empty string for any conversation that did not arrive over WhatsApp (web chat, email, Slack, etc.), and for a WhatsApp conversation where the phone metadata is genuinely unavailable. It never falls back to another channel’s identifier.


WhatsApp Profile Name

What it is: The sender’s WhatsApp profile name, exactly as Meta reports it in the inbound webhook (contacts[].profile.name). The exact variable name is {{system.whatsapp_profile_name}}.

Why use it: Greet the person by name or fill a name field in a CRM or N8N payload without asking the model to guess it from the conversation.

Format: String, free text (e.g. "Example User"). It can contain spaces, emoji, and non-Latin characters.

Example: Send the WhatsApp profile name to N8N

json
{
  "phone": "{{system.whatsapp_phone}}",
  "name": "{{system.whatsapp_profile_name}}"
}
ℹ️

Meta does not always send a profile name. When it is missing, this variable is an empty string, and the action still runs. It is also empty on any conversation that did not arrive over WhatsApp. Chipp never guesses a name from the message text or the phone number.

⚠️

The profile name is user-controlled text. The sender chooses it, so treat it as untrusted input in your backend. Do not run it as code, and validate or escape it before you put it in SQL, HTML, or shell commands.


Combining System Variables

System variables work alongside application variables and AI-generated parameters:

json
{
  "api_key": "{{var.API_KEY}}",
  "session": "{{system.session_id}}",
  "metadata": {
    "user": "{{system.user_id}}",
    "timestamp": "{{system.timestamp}}",
    "query": "{{searchQuery}}"
  }
}

In this example:

  • {{var.API_KEY}}: Your stored API key (application variable)
  • {{system.session_id}}: Current chat session (system variable)
  • {{system.user_id}}: Current user (system variable)
  • {{system.timestamp}}: Current time (system variable)
  • {{searchQuery}}: Value the AI determines from context (AI-generated)

Security Best Practices

1. Use Secret Type for Sensitive Data

Mark sensitive values as secret:

bash
Type: secret
Name: API_KEY
Value: sk-abc123...

Type: string
Name: API_KEY
Value: sk-abc123...

2. Never Expose Secrets in URLs

bash
https://api.example.com/auth?key={{var.API_KEY}}

Headers:
  Authorization: Bearer {{var.API_KEY}}

3. Scope Variables Appropriately

Variables are scoped to applications, not global:

  • Each app has its own variable namespace
  • No cross-app variable access
  • User-specific isolation

4. Rotate Keys Regularly

Update variable values without changing actions:

  1. Go to Variables tab
  2. Update the value
  3. All actions automatically use new value

Common Patterns

Environment Configuration

Manage different environments with variables:

bash
API_DOMAIN = api.dev.example.com
ENVIRONMENT = development

API_DOMAIN = api.example.com
ENVIRONMENT = production

Multi-Tenant Setup

Use variables for tenant-specific values:

bash
WORKSPACE_ID = ws_123456
TENANT_KEY = tenant_abc
ORG_ID = org_789

API Versioning

Control API versions centrally:

bash
API_VERSION = v2
SCHEMA_VERSION = 2024-01-15
CLIENT_VERSION = 1.2.3

Variable Management

Viewing Variable Usage

See which actions use a variable:

  • Check before deleting variables
  • Update impacts all actions

Import/Export

When exporting actions to collections:

  • Variable definitions included
  • Variable values NOT exported
  • Recipients set their own values

Testing Variables

Use the action tester to verify resolution:

bash
URL: {{var.BASE_URL}}/users
Header: Bearer {{var.API_KEY}}

URL: https://api.example.com/users
Header: Bearer sk-abc123...

Placeholders and Parameter Sources

You can reference a variable two ways:

  • Pick it as the parameter’s source. Set the source dropdown to Application variable and type the name, or to System variable and pick one from the list. The whole value comes from the variable.
  • Type a placeholder into a fixed value. Use this when the variable is only part of the text, for example Bearer {{var.API_KEY}}.

In a fixed value, the placeholders resolve like this:

bash
{{var.API_KEY}}     # Application variable (fails clearly if it does not exist)
{{system.user_id}}  # System variable (fails clearly if the name is unknown)
{{API_KEY}}         # Application variable if one has that name, otherwise sent as typed

A placeholder with no namespace is not filled by the AI in a fixed value. To have the AI write a value, set the parameter’s source to AI fills it in.

The cURL importer is the one exception: when you import a cURL command, a value that is exactly {{name}} becomes an AI fills it in parameter, {{var.NAME}} becomes an Application variable parameter, and {{system.NAME}} becomes a System variable parameter when NAME is one listed in Available System Variables.

Troubleshooting

Variables Not Resolving

  1. Check Variable Name

    • Case-sensitive
    • No spaces in names
    • Correct namespace (var. or system.)
  2. Check Variable Definition

    • Variable exists in Variables tab
    • Has a value set
    • Correct variable type
  3. Check Syntax

    • Double curly braces: {{}}
    • No extra spaces: {{var.NAME}}
    • Correct namespace prefix

Secret Values Not Visible

Secrets are encrypted and never shown after saving:

  • ✓ This is expected behavior
  • ✓ Update value to change
  • ✓ Use tester to verify working

Cross-Action Dependencies

Variables are resolved per action:

  • Define once, use everywhere
  • Changes apply immediately
  • No action rebuild required

Next Steps