Skip to main content

Overview

A custom variable record only becomes call data once it’s connected to a contact. When your agent calls (or is called by) a contact, it reads the custom variable records linked to that contact and exposes their fields to your script through the custom namespace — for example {{custom.property_listing.address}} (string, e.g. 12 Wattle St, Brisbane QLD 4000). Linking is flexible in both directions:
  • One record, many contacts — link the same Property Listing to every buyer you’re calling about it.
  • One record per contact — give each survey respondent their own answers record.
This page covers every way a link is made — the contact detail pane, the bulk toolbar on Contacts, CSV import, the API and automations — plus how the data is stored and exactly what the agent reads on a call.

What the agent reads on a call

At the start of every call, the platform builds the contact’s custom variable values in two passes:
  1. Type defaults — custom variable types installed from the catalogue ship with a default record, and those defaults are loaded first, so {{custom.<type name>.<field>}} references resolve even for contacts with nothing linked. Types you build yourself carry no defaults — their fields read as empty until a record is linked.
  2. Linked records — the records linked to the contact are laid over those defaults. A linked record’s values win wherever they’re set.
In any script field that supports variables, type / to insert these tokens — your installed types appear under the custom branch of the picker with each field’s data type shown. Keep one record of each type linked per contact. The workspace flows enforce this — the contact detail pane shows one slot per type, and bulk assignment replaces any existing link of the same type — but the API allows several. If multiple records of one type are linked, their values are combined into a single view in no guaranteed order, so overlapping fields become unpredictable. Two authoring consequences:
  • A field that’s empty on the linked record and has no default reads as empty. In spoken text an empty variable is left as the literal token (and the call gets a system note flagging the unresolved reference), so guard optional fields before speaking them — see Text Functions for conditional text.
  • Changes you make to a record between calls flow through automatically — the values are read fresh at the start of each call.

Linking from a contact’s details

Open Contacts, click a contact to open its details, and scroll to the Objects section — this is where the contact’s linked custom variables live, with one row per custom variable type: The dropdown lists every record of that type that isn’t already linked to the contact; if the type has no records yet, create one in the Custom Variables workspace first. Unlinking only removes the connection — the record itself stays in the Custom Variables workspace and any links to other contacts are untouched.

Bulk assigning from the Contacts table

To link the same record to many contacts at once:
  1. On Contacts, tick the checkboxes of the contacts you want.
  2. In the toolbar that appears, click the Assign Object button (box icon).
  3. In the popover, choose the Object type, then the Value — the record of that type to link.
  4. Click Apply.
Bulk assignment is a replace, not an add: any existing link of the same type on the selected contacts is cleared first, so every selected contact ends up linked to exactly the record you chose. Links of other types are untouched. This is the pattern for shared data — a promotion, an event, a listing — where every contact in a campaign should hear the same values.

Seeing linked values on the Contacts table

The Contacts toolbar has a view toggle (box icon) whose tooltip reads Show objects — click it to switch the table into the custom variables view. The contact detail columns are replaced by one sortable column per custom variable type, showing the name of the record linked to each contact (or - when nothing is linked; multiple linked records show as a comma-separated list). Click the toggle again (Show contact details) to switch back. Use this view to spot-check a list before launching a batch: an unlinked contact hears the type’s default values (or nothing at all, if the type has no defaults), not record-specific ones.

Creating one record per contact (CSV import)

When each contact needs their own values — survey answers, order details, account balances — importing a CSV is faster than creating and linking records one at a time. The contact importers (on Contacts, Lists and the Batch Scheduler) include a Per-contact custom variables step: pick a custom variable type, map your spreadsheet columns to its fields, and the import creates or updates one linked record per contact. The update logic is an upsert, keyed on the contact:
  • If the contact already has a record of that type linked, that record’s data is updated in place.
  • Otherwise a new record is created — named after the contact, falling back to their phone number — and linked.
Values are checked against the type’s schema as they’re imported; whether problems warn or block depends on the type’s validation setting — see Custom Variable Types & Schema Validation. Up to 5,000 rows of custom variable data can be imported at once. The column header convention (custom.<type name>.<field>), typed templates and mapping rules are covered on Importing Custom Variables from CSV.

How the data is stored: linked records vs on-contact data

Every custom variable type has a storage behaviour, set by the unique_by_contact flag when the type is created through the API (types created in the app use the default, false): The values your scripts read through the custom namespace come from linked records. Data stored on the contact by a unique-per-contact type serves platform and integration workflows instead — for example, calendar integrations use it to remember a contact’s booking identifiers between calls — and it’s returned with the contact through the API. There is no control for this flag in the app — set it when creating a type through the API. Speak to the Voxworks team if you need an existing type’s storage behaviour changed.

Linking through the API

The Voxworks API covers the full round trip. In brief:
  • Attaching data when scheduling a call — the create-phone-call endpoint accepts an objects payload: a map of type name to field values. Each entry is validated against the type’s schema, then stored according to the type’s unique_by_contact behaviour — merged onto the contact, or saved as a record and linked. If the data carries an id matching an existing record of that type, that record is updated rather than duplicated.
  • Upserting records — the upsert endpoint matches an existing record by a key inside its data. Pass an upsert_key with a JSON path (for example $.id) and value; if the data itself contains an id field it’s used as the default key. A match updates the record in place — keeping every existing contact link — otherwise a new record is created. This is what makes repeated syncs from an external system safe: the same order or listing never duplicates.
  • Linking and unlinking — a dedicated endpoint links an existing record to an existing contact by their IDs. Records deleted through the API are hidden rather than destroyed — they drop out of the Custom Variables workspace and out of link pickers, but existing contact links are not automatically removed.

Linking through automations

The Upsert Custom Variable pipeline step gives Automations the same create-or-update-and-link ability, driven by trigger data: After the step runs, later steps in the same pipeline can read the saved values as custom.<type name>.<field> in their own field maps — see Field Mapping & Context. A typical pattern: a webhook from your CRM fires an automation that upserts an Order record, links it to the contact, then creates a call — so the agent dials with {{custom.order.due_date}} (string, e.g. 2026-03-01) already populated.

Next Steps