Idriss
12/19/2025, 3:54 PMAI Evy
12/19/2025, 3:54 PMAI Evy
12/19/2025, 3:54 PM{{variable_name}} syntax with double curly braces. [(1) ]()
Key points:
- Reference variables directly by name: {{customer_name}}, {{order_id}}, etc.
- Spaces around the variable name will be trimmed automatically when evaluated.
- Use the variable name directly (e.g., {{make}}) rather than dot notation like scheduling_info_extraction.make — dot notation won't work. [(2) ]()
Example in a prompt:
"Hello {{user_name}}, I understand you're interested in {{product_name}}. How can I help you today?"
[(1) ]()
Handling missing variables:
If a variable has no value, it remains in raw form with curly braces intact. You can add conditional logic in your prompt:
If {{user_name}} appears with curly braces, use a generic greeting.
Otherwise, greet the customer by name.
You can also check if a variable exists in conversation flow equations using:
Equation: {{user_name}} exists
Result: True if variable is setAI Evy
12/19/2025, 3:54 PMAI Evy
12/19/2025, 3:54 PMIdriss
12/19/2025, 3:56 PMIdriss
12/19/2025, 3:58 PMAI Evy
12/19/2025, 3:58 PMAI Evy
12/19/2025, 3:58 PM{{variable_name}} [(1) ]()
Example prompt language:
If your dynamic variables include fields like customer_name, appointment_date, or order_id, you would write prompts like:
"Hello {{customer_name}}, I see you're calling about order {{order_id}}."
Important formatting notes:
- No spaces inside the braces — use {{provider_info}} not {{ provider_info }} [(2) ]()
- Spaces around the variable name are trimmed automatically when evaluated [(1) ]()
- All dynamic variable values must be strings
Handling potentially empty variables:
Add conditional logic in your prompt:
If {{user_name}} appears with curly braces, use a generic greeting.
Otherwise, greet the customer by name.
If you can share the specific variable names you're working with (typed out) , I can help you craft the exact prompt language for your use case.AI Evy
12/19/2025, 3:58 PMAI Evy
12/19/2025, 3:58 PMIdriss
12/19/2025, 4:03 PMAI Evy
12/19/2025, 4:03 PMAI Evy
12/19/2025, 4:03 PM{{variable_name}} syntax: [(1) ]()
Your variables mapped to prompt syntax:
- Business Name → {{Business Name}} or {{business_name}} (depending on how you passed them)
- Call Direction → {{Call Direction}} or {{call_direction}}
- Email → {{Email}} or {{email}}
- First Name → {{First Name}} or {{first_name}}
- Last Name → {{Last Name}} or {{last_name}}
- Niche → {{Niche}} or {{niche}}
- Phone Number → {{Phone Number}} or {{phone_number}}
Example prompt in a node:
"Hello {{First Name}}, I'm calling from {{Business Name}}. I understand you're in the {{Niche}} industry. How can I help you today?"
Important: The variable name inside the curly braces must match exactly how you passed them in your retell_llm_dynamic_variables field when creating the call.
If you passed them with spaces (e.g., "First Name": "Test") , use {{First Name}}. If you used snake_case (e.g., "first_name": "Test") , use {{first_name}}.
Check your API call payload to confirm the exact key names you're using.AI Evy
12/19/2025, 4:03 PMAI Evy
12/19/2025, 4:03 PMIdriss
12/19/2025, 4:06 PMAI Evy
12/19/2025, 4:06 PMAI Evy
12/19/2025, 4:06 PM{{Call Direction}} = outbound is the right way to set up a condition that checks if your dynamic variable equals a specific value. [(1) ]()
A few things to verify:
1. Exact variable name match — Make sure Call Direction matches exactly how you passed it in your API call (including spaces and casing) . If you passed it as call_direction, use {{call_direction}} = outbound instead. [(2) ]()
2. String matching — The value outbound should match exactly what you're passing. Ensure there are no extra spaces or different casing in your API payload.
3. Else destination — Logic Split Nodes always require an else destination as the default path if none of the conditions are met. Make sure you have that configured. [(1) ]()
Example condition setup:
{{Call Direction}} = outbound
If the condition isn't working as expected, double-check your API payload to confirm the exact key name and value format you're sending under retell_llm_dynamic_variables. [(2) ]()AI Evy
12/19/2025, 4:06 PMAI Evy
12/19/2025, 4:06 PMIdriss
12/19/2025, 4:28 PMAI Evy
12/19/2025, 4:28 PMAI Evy
12/19/2025, 4:28 PMjson
{
"id": "collect_info",
"type": "conversation",
"instruction": {
"type": "prompt",
"text": "Ask the customer for their name and contact information."
}
}
So for your HVAC outbound use case, a node prompt might be:AI Evy
12/19/2025, 4:28 PMIntroduce yourself as calling from {{Business Name}}.
Greet {{First Name}} {{Last Name}} and confirm they are the right person.
If confirmed, explain you're reaching out about their {{Niche}} needs.
Keep the tone professional but friendly.
The key is keeping each node focused on one specific task or conversation stage rather than trying to handle everything in one prompt.AI Evy
12/19/2025, 4:28 PMAI Evy
12/19/2025, 4:28 PMIdriss
12/23/2025, 7:02 PMAI Evy
12/23/2025, 7:02 PMAI Evy
12/23/2025, 7:02 PMsuccess field from your response into a dynamic variable using "Set response variables," then branch based on whether {{success}} = false to route back to retry the function or handle the error gracefully.
For the response variable extraction:
You can extract values from the API response and save them as dynamic variables for use later in the conversation. [(1) ]()
Example: If your response body contains {"success": false, "error": "..."}, you can extract success as a dynamic variable, then use a Logic Split Node to check {{success}} = false and route accordingly.
I don't have documentation on a built-in "retry on application-level failure" feature within converAI Evy
12/23/2025, 7:02 PMAI Evy
12/23/2025, 7:02 PMAI Evy
12/23/2025, 7:02 PMIdriss
12/23/2025, 7:08 PMAI Evy
12/23/2025, 7:08 PMAI Evy
12/23/2025, 7:08 PMsuccess field from your API response into a dynamic variable like {{success}}.
2. Add a Logic Split Node after the function node.
3. Configure the condition — Use an equation-based condition:
- {{success}} = false → routes to your fallback/retry path
- The else destination handles the success case
Important: Make sure you're using "Response Variables" to properly set the variable value. The LLM may understand what you mean conversationally, but for equation-based conditions in Logic Split Nodes, you need to explicitly set the variable to the right value. [(2) ]()
For the condition wording:
{{success}} = false
[(1) ]() , [(3) ]()
This routes to your fallback node when the function returns a failure. The else path handles successful responses.
Note: If you're experiencing issues where the Logic Split Node always goes to else, verify that your variable is being properly populated by checking the call history for the actual value (not just the variable name) . [(3) ]()AI Evy
12/23/2025, 7:08 PMAI Evy
12/23/2025, 7:08 PMIdriss
12/23/2025, 7:10 PMAI Evy
12/23/2025, 7:11 PMAI Evy
12/23/2025, 7:11 PMjson
{
"properties": {
"user": {
"name": "John Doe",
"age": 26
}
}
}
You can extract the user's name and reference it later using {{user_name}}.
For your use case:
If your GHL API response looks like {"success": false, "error": "..."}, you would:
1. In "Set response variables," map the success field to a variable name like success
2. After the function executes, {{success}} will contain false or true
3. Use a Logic Split Node with the condition {{success}} = false to route to your fallback path
Important notes:
- If the response is an array (not an object) , you need to specify the index, like patient[0].providerNo. [(2) ]()
- Make sure the variable name you reference later matches exactly what you defined in the response variables section.AI Evy
12/23/2025, 7:11 PMAI Evy
12/23/2025, 7:11 PMIdriss
12/23/2025, 7:17 PMAI Evy
12/23/2025, 7:17 PMAI Evy
12/23/2025, 7:17 PMjson
{"result":"{\"success\": false, \"error\": \"...\", \"message\": \"...\"}"}
[(1) ]() , [(2) ]()
The path depends on your response structure:
If your response is a simple object like:
json
{
"success": false,
"error": "..."
}
Then the path would just be success. [(1) ]() , [(2) ]()
However, looking at your actual error response, it appears the data is nested inside a result field as a stringified JSON. If your endpoint returns the response in this nested format, you may need to:
1. Modify your backend to return a cleaner response structure like:
json
{
"success": false,
"error": "GHL API request failed..."
}
2. Then in "Set response variables":
- Variable Name: success
- Path: success
Example from documentation:
For a response body like:
json
{
"properties": {
"user": {
"name": "John Doe",
"age": 26
}
}
}
You would use properties.user.name as the path to extract the name. [(1) ]() , [(2) ]()
So for your case with a clean response, the path is simply success to extract the boolean value.AI Evy
12/23/2025, 7:17 PMAI Evy
12/23/2025, 7:17 PMIdriss
12/23/2025, 7:25 PMAI Evy
12/23/2025, 7:25 PMAI Evy
12/23/2025, 7:25 PMresult field as a stringified JSON string. This is the challenge — the success field isn't directly accessible at the top level.
The issue:
Your response looks like:
json
{"result":"{\"success\": true, ...}"}
[(1) ]()
The result field contains a string (not a parsed object) , so you can't use dot notation like result.success to extract it directly.
Recommended solution:
Modify your backend endpoint to return a cleaner, parsed response structure:
json
{
"success": true,
"appointment_id": "gPYKvv35v6AcMX5isBdX",
"message": "Appointment booked..."
}
, [(2) ]()
Then in your Custom Function's "Set response variables" section:
- Variable Name: success
- Path: success
After that, use a Logic Split Node with the condition {{success}} = false to route to your retry/fallback path. [(3) ]()
If you can't modify your backend:
You could parse the stringified JSON within your backend before returning it to Retell, or handle the retry logic entirely within your backend endpoint itself before returning a response.
You can also use dynamic variables inside the JSON of future function calls. [(5) ]()AI Evy
12/23/2025, 7:25 PMAI Evy
12/23/2025, 7:25 PMIdriss
12/23/2025, 7:29 PMAI Evy
12/23/2025, 7:29 PMAI Evy
12/23/2025, 7:30 PMresult field, you have a couple of options:
Option 1: Use the full response conversationally
All response formats (string, buffer, JSON object, blob) are converted to string before being sent to the LLM for further processing. [(1) ]()
This means the agent can read the entire response text and understand whether it succeeded or failed based on the content. You can prompt your conversation node to:
Check the function response. If it contains "success": false or mentions an error, apologize and offer to try again or collect the information differently.
Option 2: Use Extract Dynamic Variables function
Based on community discussions, you can use the Extract Dynamic Variables function in your prompt to have the LLM parse and extract values from the response. [(2) ]() , [(3) ]()
In your prompt, instruct the agent:
After calling the booking function, extract whether the booking was successful from the response. If the response contains "success": false, save this as {{booking_failed}} = true.AI Evy
12/23/2025, 7:30 PM{{booking_failed}} = true to route to your retry path.
Important note on response variables:
When using description-based parameters (not const values) , include the variable reference in the description field itself, like: Use this as the treatment id: {{treatment_id}}.
The limitation here is that Retell's response variable extraction works best with properly structured JSON responses where you can specify a direct path like success or properties.user.name. [(1) ]() , [(4) ]() With your stringified nested response, the LLM-based extraction approach is your best option since you cannot modify the endpoint.AI Evy
12/23/2025, 7:30 PMAI Evy
12/23/2025, 7:30 PMAI Evy
12/23/2025, 7:38 PMIdriss
12/23/2025, 7:38 PMAI Evy
12/23/2025, 7:38 PMReview the booking function response.
If the response contains "success": true, extract and save {{booking_success}} as "true".
If the response contains "success": false or mentions an error, save {{booking_success}} as "false".
Do not speak to the user in this step.AI Evy
12/23/2025, 7:38 PMAI Evy
12/23/2025, 7:38 PM{{booking_success}} = false → routes to your retry/fallback path
- Else destination → routes to your success confirmation path
Important configuration notes:
- In the Function Node, enable "Wait for Result" so the response is ready before transitioning. [(1) ]()
- The Logic Split Node always requires an else destination as the default path if none of the conditions are met. [(2) ]()
- Make sure your variable name in the Logic Split condition matches exactly what you're extracting. [(3) ]()
This approach uses the LLM's ability to read and understand the stringified response content, then saves the parsed value as a dynamic variable for the Logic Split Node to evaluate.AI Evy
12/23/2025, 7:38 PMIdriss
12/24/2025, 1:22 AMAI Evy
12/24/2025, 1:22 AMAI Evy
12/24/2025, 1:22 AM"type": "object" at the top level. [(1) ]()
Example parameter schema structure:
json
{
"type": "object",
"required": [
"order_id"
],
"properties": {
"name": {
"type": "object",
"description": "",
"properties": {
"first_name": {
"type": "string",
"description": "User first name"
},
"last_name": {
"type": "string",
"const": "{{last_name}}"
}
}
},
"order_id": {
"type": "number",
"const": 1234
}
}
}
Key points for your GHL custom fields:
- Use "description" for fields the LLM should determine from the conversation (e.g., "description": "The customer's preferred service type")
- Use "const" for values that should always be a specific value or a dynamic variable (e.g., "const": "{{customer_phone}}")
- Add fields to the "required" array if they must be present
Troubleshooting: If you get a 400 error when saving, it's likely because the parameters are not valid. One common mistake is not adding "type": "object" to the top level of the JSON schema. , [(2) ]()
Structure your properties to match whatever custom field names your GHL endpoint expects, using descriptions for LLM-resolved values and const for fixed or dynamic variable values.AI Evy
12/24/2025, 1:22 AMAI Evy
12/24/2025, 1:22 AMIdriss
12/24/2025, 1:36 AMAI Evy
12/24/2025, 1:36 AMAI Evy
12/24/2025, 1:36 AMAI Evy
12/24/2025, 1:36 AMjson
{
"type": "object",
"required": [
"business_name",
"business_niche"
],
"properties": {
"business_name": {
"type": "string",
"description": "The name of the contact's business"
},
"business_niche": {
"type": "string",
"enum": ["Auto-Dealers", "Business Loan Brokers", "Franchise Brokers", "HVAC", "MEDSPAS", "Mortgage Lenders"],
"description": "The business niche or industry category"
},
"business_website": {
"type": "string",
"description": "The contact's business website URL"
}
}
}
[(1) ]()
Key points:
- Use "type": "string" for text fields
- For enum fields, add an "enum" array with the allowed values alongside the type
- Add "description" to each field so the LLM knows what information to extract from the conversation
- Include fields in "required" if they must be present for the function call
- Make sure "type": "object" is at the top level—a common mistake is omitting this
If you want any field to use a dynamic variable instead of LLM-resolved values, replace the "description" with "const": "{{variable_name}}".AI Evy
12/24/2025, 1:36 AMIdriss
12/24/2025, 1:50 AMAI Evy
12/24/2025, 1:50 AMAI Evy
12/24/2025, 1:50 AM"type": "object" at the top level is required , [(2) ]()
- Use "description" for fields the LLM should extract from the conversation [(1) ]()
- Use "enum" array for fields with predefined options
- Add fields to "required" if they must be present for the function call
If you want any field to always use a specific dynamic variable value instead of having the LLM determine it from conversation, replace the "description" with `"const"`:
json
"business_name": {
"type": "string",
"const": "{{business_name}}"
}
This passes the dynamic variable value directly rather than having the LLM resolve it from the conversation context.AI Evy
12/24/2025, 1:50 AMAI Evy
12/24/2025, 1:50 AMIdriss
12/24/2025, 1:56 AMAI Evy
12/24/2025, 1:56 AMAI Evy
12/24/2025, 1:57 AMAI Evy
12/24/2025, 1:57 AM"type": "object" at the top level is required—omitting this is a common mistake that causes 400 errors [(1) ]() , [(2) ]()
- Use "description" for fields the LLM should extract from the conversation [(1) ]()
- For the enum field, include the "enum" array with allowed values
- Add fields to "required" if they must be present for the function call
For the phone number specifically: If you want to automatically pass the caller's phone number rather than having the LLM extract it, you can use "const" with a dynamic variable or access it from the call object that Retell passes to your endpoint:
json
"phone": {
"type": "string",
"const": "{{from_number}}"
}
, [(2) ]()
Your backend endpoint will also receive the full call object containing from_number and to_number which you can use directly. [(3) ]() , [(7) ]()AI Evy
12/24/2025, 1:57 AMjson
{
"type": "object",
"required": [
"first_name",
"phone",
"business_name",
"business_niche"
],
"properties": {
"first_name": {
"type": "string",
"description": "The contact's first name"
},
"last_name": {
"type": "string",
"description": "The contact's last name"
},
"email": {
"type": "string",
"description": "The contact's email address"
},
"phone": {
"type": "string",
"description": "The contact's phone number"
},
"business_name": {
"type": "string",
"description": "The name of the contact's business"
},
"business_niche": {
"type": "string",
"enum": ["Auto-Dealers", "Business Loan Brokers", "Franchise Brokers", "HVAC", "MEDSPAS", "Mortgage Lenders"],
"description": "The business niche or industry category"
},
"business_website": {
"type": "string",
"description": "The contact's business website URL"
}
}
}AI Evy
12/24/2025, 1:57 AMIdriss
12/24/2025, 2:01 AMAI Evy
12/24/2025, 2:01 AMAI Evy
12/24/2025, 2:01 AM{{user_number}} system variable gives you the user's phone number (inbound: from_number, outbound: to_number) .
2. Your backend can extract it from the call.from_number field in the request body that Retell sends.
[(2) ]() , [(3) ]() , [(1) ]()
For dashboard testing: Since there's no live call, you'll need to either set a default value for the phone variable in your agent settings, or test with an actual phone call to have real call context.
[(3) ]()AI Evy
12/24/2025, 2:01 AMcall object in the request body that includes from_number and `to_number`:
[(1) ]()
json
{
"call": {
"call_type": "phone_call",
"from_number": "+12137771234",
"to_number": "+12137771235",
"direction": "inbound",
"call_id": "Jabr9TXYYJHfvl6Syypi88rdAHYHmcq6",
"agent_id": "oBeDLoLOeuAbiuaMFXRtDOLriTJ5tSxD",
"call_status": "registered",
"metadata": {},
"retell_llm_dynamic_variables": {
"customer_name": "John Doe"
},
"opt_out_sensitive_data_storage": true
},
"name": "get_weather",
"args": {
"city": "New York"
}
}
Two options to pass the phone number:
1. Use const with a system dynamic variable in your parameter schema:
[(2) ]() , [(3) ]()
json
"phone": {
"type": "string",
"const": "{{user_number}}"
}AI Evy
12/24/2025, 2:01 AMAI Evy
12/24/2025, 2:01 AMIdriss
12/24/2025, 2:05 AMAI Evy
12/24/2025, 2:05 AMAI Evy
12/24/2025, 2:05 AM{{user_number}} have no value to substitute. [(1) ]()
The variable name in your JSON schema (whether phone or phone_number) doesn't matter as long as it matches what your GHL endpoint expects. What matters is how you're providing the value.
Your options:
1. Use const with the system variable:
json
"phone": {
"type": "string",
"const": "{{user_number}}"
}
[(2) ]() , [(3) ]()
This will work on live calls where {{user_number}} contains the caller's phone number. [(1) ]()
2. Set a default value for testing: Configure agent-level default dynamic variables as a fallback for when specific variables aren't included in the call request.
3. Test with an actual phone call instead of the dashboard to have real call context.
The core issue is the lack of call context during testing, not the variable naming.AI Evy
12/24/2025, 2:05 AMAI Evy
12/24/2025, 2:05 AMIdriss
12/24/2025, 2:09 AMAI Evy
12/24/2025, 2:09 AMAI Evy
12/24/2025, 2:09 AMphone field now uses "const": "{{user_number}}" instead of a description [(1) ]() , [(2) ]()
- This passes the caller's phone number directly as a fixed value rather than having the LLM extract it from conversation [(1) ]()
Remember: This will only work on live calls where {{user_number}} has an actual value. For dashboard testing, you'll need to set a default value for the variable or test with an actual phone call. [(6) ]()AI Evy
12/24/2025, 2:09 AM{{user_number}} for the phone field:
json
{
"type": "object",
"required": [
"first_name",
"phone",
"business_name",
"business_niche"
],
"properties": {
"first_name": {
"type": "string",
"description": "The contact's first name"
},
"last_name": {
"type": "string",
"description": "The contact's last name"
},
"email": {
"type": "string",
"description": "The contact's email address"
},
"phone": {
"type": "string",
"const": "{{user_number}}"
},
"business_name": {
"type": "string",
"description": "The name of the contact's business"
},
"business_niche": {
"type": "string",
"enum": ["Auto-Dealers", "Business Loan Brokers", "Franchise Brokers", "HVAC", "MEDSPAS", "Mortgage Lenders"],
"description": "The business niche or industry category"
},
"business_website": {
"type": "string",
"description": "The contact's business website URL"
}
}
}AI Evy
12/24/2025, 2:09 AMAI Evy
12/24/2025, 2:09 AMAI Evy
12/24/2025, 2:13 AMIdriss
12/24/2025, 2:13 AM{
"type": "object",
"properties": {
"business_name": {
"type": "string",
"description": "Caller's business name"
},
"custom_fields": {
"type": "object",
"description": "Additional custom field data captured during call"
},
"business_niche": {
"type": "string",
"description": "Caller's business niche"
},
"last_name": {
"type": "string",
"description": "Caller's last name"
},
"source": {
"type": "string",
"description": "Lead source"
},
"initial_notes": {
"type": "string",
"description": "Notes about the initial call conversation"
},
"business_website": {
"type": "string",
"description": "Caller's business website"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tags to categorize the contact (e.g., 'inbound-lead')"
},
"call_context": {
"type": "object",
"description": "Context about the current call",
"properties": {
"call_purpose": {
"type": "string",
"description": "Why the caller contacted the business"
},
"call_id": {
"type": "string",
"description": "ID of the current call"
}
}
},
"phone_number": {
"type": "string",
"description": "Caller's phone number (primary identifier)"
},
"company": {
"type": "string",
"description": "Company name (optional)"
},
"first_name": {
"type": "string",
"description": "Caller's first name"
},
"email": {
"type": "string",
"description": "Caller's email address (optional)"
}
},
"required": [
"phone_number"
]
}AI Evy
12/24/2025, 2:13 AMjson
{
"type": "object",
"properties": {
"business_name": {
"type": "string",
"description": "Caller's business name"
},
"custom_fields": {
"type": "object",
"description": "Additional custom field data captured during call"
},
"business_niche": {
"type": "string",
"description": "Caller's business niche"
},
"last_name": {
"type": "string",
"description": "Caller's last name"
},
"source": {
"type": "string",
"description": "Lead source"
},
"initial_notes": {
"type": "string",
"description": "Notes about the initial call conversation"
},
"business_website": {
"type": "string",
"description": "Caller's business website"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tags to categorize the contact (e.g., 'inbound-lead') "
},
"call_context": {
"type": "object",
"description": "Context about the current call",
"properties": {
"call_purpose": {
"type": "string",
"description": "Why the caller contacted the business"
},
"call_id": {
"type": "string",
"description": "ID of the current call"
}
}
},
"phone_number": {
"type": "string",
"const": "{{user_number}}"
},
"company": {
"type": "string",
"description": "Company name (optional) "
},
"first_name": {
"type": "string",
"description": "Caller's first name"
},
"email": {
"type": "string",
"description": "Caller's email address (optional) "
}
},
"required": [
"phone_number"
]
}AI Evy
12/24/2025, 2:13 AMphone_number field now uses "const": "{{user_number}}" which passes the caller's phone number directly as a fixed value rather than having the LLM extract it from conversation.
[(1) ]()
This will work on live calls where {{user_number}} contains the caller's phone number. For dashboard testing, you'll still need to set a default value or test with an actual call.AI Evy
12/24/2025, 2:13 AMAI Evy
12/24/2025, 2:13 AMIdriss
12/24/2025, 2:19 AMAI Evy
12/24/2025, 2:19 AMAI Evy
12/24/2025, 2:19 AMAI Evy
12/24/2025, 2:19 AMargs object from your schema directly to your endpoint. Your backend receives the parameters you define, so the schema should match the payload format your GHL integration expects.
Two approaches:
1. Nested under custom_fields — if your GHL endpoint expects custom fields grouped together:
json
"custom_fields": {
"type": "object",
"properties": {
"business_name": {
"type": "string",
"description": "Caller's business name"
},
"business_niche": {
"type": "string",
"description": "Caller's business niche"
}
}
}
2. Flat at the top level — if your GHL endpoint expects each field as a separate property (as in your earlier schema) .
Check your GHL API documentation or webhook handler to see what structure it expects. The JSON schema you define in Retell simply determines how the args are structured when sent to your endpoint. , [(4) ]()AI Evy
12/24/2025, 2:19 AMIdriss
12/24/2025, 2:26 AMAI Evy
12/24/2025, 2:26 AMAI Evy
12/24/2025, 2:26 AMphone_number, use const value with {{user_number}} to pass the caller's phone directly
- For fields like business_name, business_niche, business_website, use description so the LLM extracts them from conversation
- Both description and const value can include dynamic variables
Whether your custom fields populate correctly in GHL depends on:
1. Your endpoint URL receiving the data Retell sends
2. Your backend mapping those fields to GHL's custom field format
The form generates the same underlying JSON schema—it's just a visual alternative. The data Retell sends to your endpoint will be the same regardless of whether you use the form or raw JSON.AI Evy
12/24/2025, 2:26 AMAI Evy
12/24/2025, 2:26 AMAI Evy
12/24/2025, 2:37 AMIdriss
12/24/2025, 2:37 AM{
"type": "object",
"properties": {
"business_name": {
"type": "string",
"description": "Caller's business name"
},
"custom_fields": {
"type": "object",
"description": "Additional custom field data captured during call"
},
"business_niche": {
"type": "string",
"description": "Caller's business niche"
},
"last_name": {
"type": "string",
"description": "Caller's last name"
},
"source": {
"type": "string",
"description": "Lead source"
},
"initial_notes": {
"type": "string",
"description": "Notes about the initial call conversation"
},
"business_website": {
"type": "string",
"description": "Caller's business website"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tags to categorize the contact (e.g., 'inbound-lead')"
},
"call_context": {
"type": "object",
"description": "Context about the current call",
"properties": {
"call_purpose": {
"type": "string",
"description": "Why the caller contacted the business"
},
"call_id": {
"type": "string",
"description": "ID of the current call"
}
}
},
"phone_number": {
"type": "string",
"description": "Caller's phone number (primary identifier)"
},
"company": {
"type": "string",
"description": "Company name (optional)"
},
"first_name": {
"type": "string",
"description": "Caller's first name"
},
"email": {
"type": "string",
"description": "Caller's email address (optional)"
}
},
"required": [
"phone_number"
]
}AI Evy
12/24/2025, 2:37 AMAI Evy
12/24/2025, 2:37 AMAI Evy
12/24/2025, 2:37 AMphone_number using description instead of const. To fix the phone number issue from testing, change it to:
[(1) ]()
json
"phone_number": {
"type": "string",
"const": "{{user_number}}"
}
This passes the caller's phone number directly as a fixed value rather than having the LLM extract it from conversation.
Other notes on your schema:
- The custom_fields property with just "type": "object" and a description is vague—if you need specific nested custom fields for GHL, define them explicitly with their own properties
- The call_context nested object looks fine if your backend expects that structure
- Make sure "type": "object" is at the top level—a common mistake is omitting this, which causes 400 errors
Whether the custom fields populate correctly in GHL depends on your backend mapping those fields to GHL's expected format—Retell sends the args object to your endpoint as defined in your schema.Idriss
12/24/2025, 2:38 AMAI Evy
12/24/2025, 2:38 AMAI Evy
12/24/2025, 2:39 AMdescription for phone_number, which lets the LLM try to extract it from conversation—but this won't reliably capture the actual caller's phone number. The earlier schema I suggested used const to pass a fixed value. [(1) ]()
**The custom fields not populating in GHL is not a Retell issue**—it's about how your backend endpoint maps the data to GHL's API format. Retell sends the args object to your endpoint exactly as you define it in the schema. , [(2) ]()
Your backend needs to:
1. Receive the args from Retell
2. Transform them into GHL's expected custom field format (which typically requires field IDs, not just field names)
What you should check:
- GHL custom fields usually need to be passed with their specific field IDs, not just the field names like business_name
- Your webhook/backend handler needs to map your schema fields to GHL's custom field structure
This is outside Retell's scope—Retell delivers the data you define in your schema to your endpoint. The mapping to GHL's custom field format happens in your backend code.
Would you like a general answer on how GHL custom field mapping typically works, or do you have specific questions about the Retell side of the integration?AI Evy
12/24/2025, 2:39 AMAI Evy
12/24/2025, 2:39 AMIdriss
12/24/2025, 2:41 AMAI Evy
12/24/2025, 2:41 AMAI Evy
12/24/2025, 2:41 AMargs from Retell
2. Your backend code must transform those args into GHL's required format—GHL typically requires custom field IDs, not just field names
3. This transformation happens in your server code, not in Retell
For custom fields beyond phone/email with integrations like Cal.com, you need to use a custom function and handle the mapping in your own server. [(2) ]()
I can help with the Retell side of this, but the GHL custom field mapping requires work on your backend. If you don't have a backend set up, you may need to build one or use a service like Make/Zapier to handle the transformation.AI Evy
12/24/2025, 2:41 AMAI Evy
12/24/2025, 2:41 AMIdriss
12/24/2025, 2:51 AMAI Evy
12/24/2025, 2:51 AMAI Evy
12/24/2025, 2:52 AMAI Evy
12/24/2025, 2:52 AMAI Evy
12/24/2025, 2:52 AMAI Evy
12/24/2025, 3:22 AMIdriss
12/24/2025, 3:22 AMAI Evy
12/24/2025, 3:23 AMAI Evy
12/24/2025, 3:23 AMargs object with the data you've configured.
[(1) ]()
The GHL custom fields issue: From the workshop, custom fields can be added to the create contact request body in GHL:
> "Custom fields, right? So custom fields, you can just add them there as well."
[(2) ]()
Your options:
1. Build a custom function endpoint — Create your own backend that receives Retell's data and maps it to GHL's custom field format (which typically requires field IDs) . Retell sends the request to your specified URL with the function name and parameters.
[(1) ]()
2. Use the update contact approach — Create the contact first, then use a separate function to update it with custom fields if your current create function is limited.
3. Check GHL's API documentation — GHL custom fields usually need to be passed with their specific field IDs, not just field names.
For integrations requiring custom fields beyond standard ones, using a custom function with your own server to handle the mapping is the recommended approach.
[(3) ]()
Is there a specific part of the Retell configuration you'd like help with?AI Evy
12/24/2025, 3:23 AMIdriss
12/24/2025, 3:23 AMAI Evy
12/24/2025, 3:23 AMAI Evy
12/24/2025, 3:24 AMtraceId without slots
4. Attach different Conversation Nodes for each path — one to present available slots, another to handle the fallback (e.g., apologize and offer alternative options)
The transition conditions would evaluate the function result. For example, your failure response only has traceId, while success has date keys with slots arrays — you can base your condition on whether slots exist in the response.AI Evy
12/24/2025, 3:24 AMAI Evy
12/24/2025, 3:24 AM