Guides
Learn how to create and enrich Thoughtly contact records with attributes, use On Inbound Call for real-time personalization, and write data back after every call. Step-by-step guide with insurance, mortgage, and education examples.
Last updated
A caller who hears their name, their quote details, and the last conversation they had with your company is more likely to stay on the line and book. That kind of personalization does not happen by default β it requires structured contact data, consistent attribute mapping, and agent prompts that know how to use it.
This guide walks through how to set up contact-level context in Thoughtly so every AI call β inbound or outbound β starts with the right background information. You will learn how to create and enrich contact records, use the Audiences table, map CRMCRMThe system of record for leads, contacts, deals, and activity. Thoughtly reads from and writes to your CRM continuously. fields to attributes, inject context via automations, and write data back after the call ends.
Everything here maps to current Thoughtly product mechanics. If a feature is not available yet, the guide says so and describes the workaround.
A Contact in Thoughtly represents a person you can interact with via phone call, SMS, or email. Every contact record includes a name, phone number, email, tags, and any custom attributes you have defined. Contacts are the foundation for personalization because they carry persistent data across every call.
The Audiences table is where you manage contacts. Access it from the primary navigation in the Thoughtly dashboard. You can search, filter, and save segments to work with specific groups of contacts β for example, all contacts with a VIP attribute, or everyone who last received a call more than 30 days ago.
Contact records power two important capabilities:
If you use Call Phone Number instead of Call Contact, you get a one-off call with no persistent linkage. For any workflowWorkflowAn automated, multi-step process β usually triggered by an event (form fill, new lead) and orchestrating one or more voice / SMS / email actions. where personalization matters, always prefer Call Contact.
Before you can personalize calls, you need contact records with meaningful attributes. There are three ways to get data onto a contact:
If you use HubSpot, Salesforce, GoHighLevel, Pipedrive, Zoho, or Keap, connect the integration in Settings β Integrations. Once connected, map CRM fields to Thoughtly contact attributes:
| CRM field | Thoughtly attribute | Example value |
|---|---|---|
| First name | first_name | Jordan |
| Phone | phone | +15551234567 |
| Lead source | lead_source | website_form |
| Lifecycle stage | lifecycle_stage | qualified_lead |
| Policy type | policy_type | auto_insurance |
| Last appointment | last_appointment_at | 2026-06-15T10:00:00Z |
Use stable, descriptive attribute names in lower_snake_case so agents and automations can reference them consistently. The integration syncs bidirectionally β CRM updates flow into Thoughtly, and call outcomes write back to the CRM.
Use the Create or Update Contact step (an upsert) in any automation to create a contact or update an existing one. This is useful when the data source is a webhookWebhookAn event-based integration that sends data from one system to another when something happens, such as a form submission, booked appointment, or completed call., a form submission, or another automation.
Related steps you can chain together:
For custom integrations, use the Thoughtly Contact API. The POST /contact/create endpoint creates a new contact, and POST /contact/{id}/update_info updates an existing one. Both accept custom attributes as part of the payload.
// Create a contact with attributes
const response = await fetch('https://api.thoughtly.com/contact/create', {
method: 'POST',
headers: {
'x-api-token': 'your_api_token',
'team_id': 'your_team_id',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone_number: '+15551234567',
name: 'Jordan Patel',
email: '[email protected]',
custom_fields: {
lead_source: 'referral',
policy_type: 'auto_insurance',
preferred_language: 'spanish',
vip: true
}
})
});Thoughtly has two data layers that feed context into calls. Understanding the difference is critical for getting personalization right.
| Feature | Attributes | Metadata |
|---|---|---|
| Scope | Persistent on contact | Single call only |
| Lifespan | Survives across all future calls | Discarded after the call ends |
| Use cases | Customer preferences, status, demographics | Campaign labels, A/B testing, per-call context |
| Set from | Automations, API, CRM sync, manual updates | Call Phone Number or Call Contact automation steps |
| Access in agent | {{system.contact.attributes.ATTRIBUTE_NAME}} | {{metadata.VARIABLE_NAME}} |
| Example | preferred_language: "spanish" | campaign: "summer_promo" |
The rule of thumb: if you will reuse the data on future calls, store it as an attribute. If it only matters for this specific call, use metadata.
A practical insurance example: store the lead's state, policy type, and consent status as attributes (they persist). Pass the current campaign name and call-back window as metadata (they are temporary).
When triggering an outbound call from an automation, use the Call Contact step β not Call Phone Number β so the agent has access to the contact's persistent attributes.
In the Call Contact step, you can also pass metadata for this specific call:
{
"contact_id": "{{ steps.create_or_update_contact.contact_id }}",
"agent_id": "qualification_agent",
"metadata": {
"campaign": "q4_renewal",
"intent": "book_consultation",
"priority": 2
}
}Inside the agent, reference these values in an advanced prompt:
The contact's name is {{ system.contact.first_name }}.
Their lead source is {{ system.contact.attributes.lead_source }}.
Their policy type is {{ system.contact.attributes.policy_type }}.
If the source is "referral", thank them for being referred
before asking qualification questions.
This call is part of the {{ metadata.campaign }} campaign.
If {{ metadata.intent }} is "book_consultation", offer
available consultation times.Use the Test Agent (text chat) with sample metadata to verify the prompt behaves correctly before going live. You can inject test metadata as JSON in the test panel:
{
"first_name": "Jordan",
"lead_source": "website_form",
"policy_type": "auto_insurance",
"priority": "high"
}Inbound calls are where contact-level context has the highest impact. When a returning lead calls in, your agent should already know who they are, what they asked about last time, and what the next step should be.
The On Inbound Call triggerTriggerThe event or condition that starts an automated workflow, such as a new lead, missed call, CRM status change, calendar booking, or completed call. fires before the call connects to an agent. Use it to look up the caller, fetch CRM data, and set attributes or metadata that the agent will reference during the conversation.
Here is a recommended automation pattern for inbound personalization:
The On Inbound Call trigger outputs the caller number, dialed number, timestamp, and available carrierCarrierA telecommunications provider that routes phone calls and SMS over its network. Twilio, Telnyx, and Bandwidth are the three most common in the AI voice space. metadata. Use these values in your automation steps.
After a call ends, use the On Call Completed trigger to update the contact with new information learned during the conversation. This ensures the next call β whether it is from your team or another Thoughtly agent β has the latest context.
Recommended post-call attributes to write:
Use the Add Attributes to Contact step in an On Call Completed automation. Map the values from the call payload β for example, {{ trigger.outcome }} for the call outcome, or {{ steps.ai_summary.summary }} for an AI-generated summary.
You can also copy metadata from the call into permanent attributes. If you passed a campaign label as metadata, write it into an attribute like last_campaign so it persists for reporting.
Inside the Agent Builder, you can reference contact attributes in Speak nodes (Prompt mode), Advanced prompts, and mid-call Actions. Use the bolt icon (β‘) in the prompt editor to insert variables.
Available system variables for contact context:
For metadata (per-call only):
A practical mortgage example:
You are calling {{ system.contact.first_name }} about their
mortgage inquiry. Their loan type is
{{ system.contact.attributes.loan_type }} and their state is
{{ system.contact.attributes.state }}.
If the loan type is "refinance", mention today's refinance
rates before asking about their timeline.
This call is from the {{ metadata.campaign }} campaign.
Focus on booking a consultation with a loan officer.Call Phone Number places a one-off call with no contact linkage. If you want attributes to persist, use Call Contact with a valid contact_id. This is the single most common personalization mistake.
Metadata is discarded after the call. If you store a lead's preferred language or consent status in metadata, it will not be available on the next call. Use attributes for anything you need to reuse.
The Test Agent supports sample metadata, but many teams skip this step. Test your prompts with realistic attribute values before going live. A prompt that references {{ system.contact.attributes.lead_source }} will behave differently when that field is empty versus when it contains "referral".
Attributes persist on the contact record and are visible in the Audiences table. Do not store sensitive PIIPersonally Identifiable Information (PII)Any data that can identify an individual β name, phone, SSN, account number. Voice agents must redact and protect PII per privacy law. you do not need. Keep attributes to operational fields β preferences, status, routing flags β and leave sensitive data in your CRM.
If you do not update attributes after the call, the next agent will not know what happened. Always set up an On Call Completed automation that writes last_called_at, last_outcome, and any qualifying answers back to the contact.
To evaluate whether contact-level context is improving call outcomes, track these metrics:
| Metric | What to measure | Where to find it |
|---|---|---|
| Booking rate lift | Compare booking rates for calls with vs without personalized context | Thoughtly Analytics + CRM reports |
| Talk time | Shorter calls with higher completion rates indicate efficient personalization | History β call duration |
| Repeat call rate | Fewer repeat calls about the same topic suggests context is working | Contact timeline + History |
| Attribute coverage | % of contacts with key attributes populated (lead_source, preferred_language, etc.) | Audiences table export |
| Post-call data quality | Completeness of attributes written back after calls | Audiences table + CRM sync audit |
Set a baseline before enabling personalization, then compare the 30-day window after. Look for improvements in booking rate and reductions in repeat-call volume β both indicate that callers are getting their needs met in fewer interactions.
Attributes are persistent fields stored on the contact record. They survive across calls and can be set from automations, the API, or CRM syncCRM syncCRM sync is the two-way flow of lead records, conversation notes, outcomes, and next steps between an AI agent platform and a CRM so human teams inherit current pipeline instead of manual updates.. Metadata is temporary β it only exists for a single call and is discarded afterward. Use attributes for facts you will reuse; use metadata for per-call context like campaign labels or A/B variant flags.
Yes. Use the On Inbound Call trigger to look up the caller by phone number, fetch their attributes, and inject context before the call connects to the agent. This is the most effective way to personalize inbound calls.
Use the Create or Update Contact step earlier in the same automation, or use Get Contact by Phone Number to find an existing contact. Both steps return a contact_id you can reference in subsequent steps.
Yes. Attributes persist, but metadata is per-call. You can call the same contact multiple times with different metadata β for example, a different campaign label on each call β while their attributes (like lead_source or preferred_language) stay consistent.
The variableVariableA named value the voice agent stores during a conversation β caller name, intent, qualifying answers β and uses to drive routing and post-call actions. resolves to an empty string. If your prompt depends on a specific attribute value, add a fallbackFallbackA safe backup path used when the caller says something unexpected, an integration fails, or the agent cannot confidently complete the intended step. instruction β for example: "If {{ system.contact.attributes.lead_source }} is empty, ask how they heard about us."