Skip to main content
Custom functions extend the agent beyond built-in actions like SMS and transfer. You define a function in the dashboard (name, description, your URL, what parameters the agent can pass), and the agent can call it during the conversation — to look up customer records, check inventory, book appointments, anything your API can do.

How it works

1

Create the function in the dashboard

Open your agent, expand the Custom Functions panel, and add a function. You give it a name, a description (the agent reads this every turn), an HTTPS URL, and a parameter schema.
2

The agent sees it as a tool

On every conversation turn, the agent is aware of every active function on the agent. Based on the description you wrote, the agent decides when to call it.
3

Engine calls your URL

When the agent triggers a function, the engine makes an HTTP request to your URL with a JSON body containing the call context, what the agent extracted, and 14 dynamic variables.
4

Your endpoint replies with JSON

You return a JSON response. The engine reads the result field (if present) and feeds it back to the agent as the tool result.
5

The agent responds to the caller

The agent uses the data you returned to compose the next reply, spoken naturally to the caller via TTS.

What your endpoint receives

Every request from the engine looks like this:
{
  "name": "check_appointment",
  "args": {
    "date": "2026-03-08",
    "time": "14:00"
  },
  "variables": {
    "caller_phone": "+447386172392",
    "business_phone": "+447446466847",
    "current_date": "Wednesday, May 27, 2026",
    "current_iso_date": "2026-05-27",
    "current_time": "14:32",
    "current_iso_time": "14:32:18",
    "tomorrow_date": "Thursday, May 28, 2026",
    "tomorrow_iso_date": "2026-05-28",
    "current_day_of_week": "Wednesday",
    "current_timezone": "Europe/London",
    "call_direction": "inbound",
    "call_id": "CAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "agent_id": "agent_f2b06c0e0bd877e9",
    "caller_history": "PREVIOUS CALLS FROM THIS NUMBER..."
  }
}
Three top-level fields:
FieldWhat it is
nameThe name of the function being called.
argsWhat the agent extracted from the conversation, matching your parameter schema.
variablesAll 14 dynamic variables. Sent on every request automatically — you do not need to ask for them.
The only header you receive is User-Agent: Nixflex-VoiceEngine/1.0. There is no signature header yet (see Authentication below).

The 14 dynamic variables

These are always present in variables on every request. They cover everything the engine knows about the call.
VariableDescriptionExample
caller_phoneCaller phone in E.164 format+447386172392
business_phoneThe number the caller dialed+447446466847
current_dateToday, human-readable (agent timezone)Wednesday, May 27, 2026
current_iso_dateToday, ISO format2026-05-27
current_timeNow, 24h HH:MM14:32
current_iso_timeNow, ISO time format14:32:18
tomorrow_dateTomorrow, human-readableThursday, May 28, 2026
tomorrow_iso_dateTomorrow, ISO format2026-05-28
current_day_of_weekDay nameWednesday
current_timezoneAgent timezone settingEurope/London
call_directioninbound or outboundinbound
call_idThe Twilio call SIDCAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
agent_idThe agent’s IDagent_f2b06c0e0bd877e9
caller_historySummaries of past calls from this number (empty for first-time callers)PREVIOUS CALLS FROM THIS NUMBER...
There is no caller_name variable. Speech-to-text often mishears names. If a name matters to your flow, instruct the agent in its system prompt to ask the caller to spell it and confirm it back before calling the function.

What to send back

Your endpoint should respond with JSON. Two valid shapes: Recommended — wrap the data in a result field:
{
  "result": {
    "available": true,
    "alternatives": ["14:30", "15:00", "15:30"]
  }
}
The engine extracts result and feeds just that to the agent. Cleaner context, faster agent responses. Also accepted — return the data at the top level:
{
  "available": true,
  "alternatives": ["14:30", "15:00", "15:30"]
}
If there’s no result field, the engine stringifies the whole body and feeds it to the agent.

Status codes

CodeBehaviour
200Success. Body is parsed and passed to the agent.
4xx / 5xxTreated as a function error. The agent is told the function failed and can apologise or transfer naturally.
No response within timeoutTreated as a function error. Default timeout is 5 seconds.
Errors never crash the call — the agent always handles them gracefully.

Writing a good description

This is the most important field on the whole function. The agent reads your description on every conversation turn to decide whether to call the function. A vague description means the agent either never fires it, or fires it at the wrong time. Bad:
Helps the AI know when to call this function
The agent has no idea when this applies. The function effectively doesn’t exist. Good:
Check the status of a customer's order. Use when the caller asks about delivery, order status, where their order is, or anything about a previously placed order.
The agent now knows exactly when to fire. Will trigger reliably whenever the conversation matches.
Write the description as instructions to the agent, not as a label for humans. Always include the words “Use when…” followed by the conversational cues that should trigger it.

URL and header variable substitution

Even though all 14 variables are sent in the request body automatically, sometimes you need them in the URL itself (REST APIs) or in a header (authentication). Use {{variable_name}} syntax in the URL or Headers fields.

URL substitution

https://api.example.com/customers/{{caller_phone}}/orders
Becomes:
https://api.example.com/customers/+447386172392/orders

Header substitution

Headers also support substitution, useful for passing call context through to upstream services that expect it in headers.
X-Call-Id: {{call_id}}
X-Caller-Phone: {{caller_phone}}

When to use which

Your API styleWhere data goesUse
REST resource (GET /customers/{id})URL path{{variable}} in URL
REST query (GET /search?phone=x)URL query string{{variable}} in URL
JSON POST endpointRequest bodyRead from req.body.variables
Most modern APIs use the third pattern, so most developers leave the URL static and just read variables from the body. URL substitution is for REST-style and GET endpoints.
Substitution only works in URL and Headers. It does not substitute in the description, name, or parameter schema fields.

Authentication

Custom function endpoints are public by default — the engine identifies itself with User-Agent: Nixflex-VoiceEngine/1.0 but does not yet sign requests. For now, the recommended pattern is a bearer token in the Headers field:
Authorization: Bearer your-shared-secret-here
Add it once in the Headers field on the function, and the engine sends it on every request. Verify it on your side before processing.
Use a long random shared secret (32+ characters). Treat it like a password — anyone with it can call your function URL pretending to be Nixflex.
Request signing (HMAC with rotating secrets) is on the roadmap. When it lands, the engine will send an X-Nixflex-Signature header you can verify with a per-agent secret. Until then, use bearer tokens in Headers.

Mentioning functions in the prompt

You can let the agent discover when to use functions purely from the descriptions, but adding an Actions section to the system prompt makes triggering more reliable:
ACTIONS
- Before promising any appointment time, check availability first using the check_availability function.
- Once the caller confirms a time, create the booking using the create_booking function.
- For order status questions, look up the order using the get_order function.
This gives the agent an extra hint about ordering and conditions on top of each function’s description.

Common use cases

Calendar lookups

Check availability before promising a slot.

CRM record fetches

Pull customer history when they call.

Booking creation

Create the booking in your system once confirmed.

Inventory checks

Verify a product is in stock before quoting.

Order status

“Where’s my order?” — look it up and reply naturally.

Payment links

Generate a Stripe checkout URL and text it via [SEND_SMS:].

Limits and constraints

LimitValueWhy
Functions per agent10Keeps the agent’s tool list focused. Too many functions and the agent gets confused about which to call.
URL schemeHTTPS onlyPlain HTTP is rejected for security.
Default timeout5 secondsAfter this, the function is treated as failed. Override per-function if your API is slow.
Response size100 KB maxLarger responses are rejected. Big payloads also bloat the agent’s context.
Tool rounds per turn3Stops the agent from looping on the same function indefinitely.
Function name formatsnake_case, max 64 charsMatches the agent’s tool name requirements.

Limitations

  • Functions are triggered by the agent during conversation — you cannot trigger them externally.
  • The engine does not retry failed functions automatically. Make your endpoint idempotent if the agent might call it twice.
  • Function calls happen per conversation turn. There is no streaming partial results back to the agent.
  • The tool-aware path is slightly slower than the no-functions path because it waits for the full agent response (including tool calls) before speaking.

Testing your function

The dashboard has a Test Webhook button on every function. It sends a request to your URL with:
  • Fake caller_phone (+44XXXXXXXXXX)
  • Fake business_phone (+44XXXXXXXXXX)
  • Fake call_id (test_<timestamp>)
  • Real date/time variables (from your agent’s timezone setting)
  • Args you type into the Sample Args field
Use it to confirm your endpoint is reachable, returns the right shape, and responds within the timeout. The actual production call will use real caller numbers and real call_id.
The test panel is reachability-focused. To test a complete real-world flow with a real caller number, just make a real call to your agent.
For more standardised integrations (Stripe, Salesforce, Google Calendar etc.), MCP server support is on the roadmap.