This guide is intended for advanced Natterbox admins. Further customisations of this solution might not be supported.
The Script Engine app runs custom Lua scripts within a routing policy, giving you full programmatic control over session properties, variables, and call-flow logic that goes beyond what the standard Policy Builder apps can achieve. This is not an exhaustive list of all the Script Engines capabilities but highlights some of its functionality and provides specific use cases.
Available in the Call Policy? | Available in the Data Analytics Policy? | Available in the Digital Policy? | Available in the System Policy? |
|---|---|---|---|
✔ | ✔ | ✔ | ✔ |
Purpose: Execute custom Lua code at any point in a routing policy — for setting session properties, querying Salesforce, collecting caller input, performing calculations, or implementing logic that cannot be achieved with the standard app library.
Location: Action container, within a Call or Data Analytics routing policy.
What is the Script Engine app?
The Script Engine app embeds a Lua code editor directly into the Policy Builder. When a call or data event reaches this app, Natterbox executes the script in real time. The script can read and write session properties, query Salesforce, collect DTMF input from callers, connect calls, and set values that downstream apps and macros can reference.
This makes it the most flexible app in the Policy Builder — but also the most advanced. It is typically used by experienced administrators or deployed by Natterbox professional services for scenarios that require logic beyond what the drag-and-drop apps provide.
How script execution works
Lua functions in the Script Engine fall into two categories:
Execute and Resume — the function runs, blocks until complete, then the script continues on the next line. Most functions work this way (e.g.
session.set(),session.say(),session.ivr()).Execute and Terminate — the function runs and the script exits immediately. The call flow continues in the policy after the script finishes. Only a few functions work this way:
session.connect(),session.conference(), andsession.hangup().
Understanding this distinction is important: if you call session.connect(), any code after it will never execute because the script terminates at that point.
⚠️
Warning: Scripts that contain errors or infinite loops can cause calls to fail or hang. Always test scripts thoroughly in a non-production policy before deploying to live call flows.
Before you begin
A Call or Data Analytics routing policy open in the Policy Builder, with an Action container ready to hold the app.
Basic Lua knowledge — familiarity with variables, strings, tables, conditional statements, and string concatenation (
..operator).Clear requirements — know what session property you need to set or what logic you need to implement, as this app has no graphical configuration beyond the code editor.
Use cases
Setting CTIText to display a custom whisper message on the agent's softphone (e.g. showing the caller's account name or the campaign they called from).
Collecting multi-digit caller input (account numbers, PINs, reference numbers) using
session.ivr()with regex validation.Querying Salesforce mid-call to look up records by caller number and route based on the results.
Creating or updating Salesforce records during the call (e.g. logging a case, updating a field).
Implementing custom time-based routing beyond what the Rule app offers (e.g. holiday calendars, timezone-aware logic).
Enabling Phone Events (missed call reporting) by running the phone events script within the policy.
Building CSAT or NPS surveys that collect multi-digit input and write scores to Salesforce.
Setting multiple session properties in a single step rather than chaining several Manage Properties apps.
Configuration reference
Give the app a name under Name this item, then open Configure this item to access the script editor.
Script editor
The configuration opens a code editor pre-populated with boilerplate code. This boilerplate includes:
connectorId— your organisation's Salesforce Data Connector ID (used withconnector.sf_query()andconnector.sf_store()).nameSpacePrefix— the Natterbox managed package prefix (typicallynbavs).The user and timestamp of when the script was created.
You write your script below the boilerplate comment. There are no tabs, dropdowns, or graphical fields — all configuration is done in code.
Trigger When and return values
The Script Engine's connector output is controlled by the return value of your script:
Return value | Behaviour |
|---|---|
| The app is triggered — the call routes out via the connector to the linked container. |
| The app is not triggered — the call passes to the next app in the container, or exits the container. |
This means you can use conditional logic in your script to decide whether to trigger the connector or not — effectively creating dynamic branching based on any criteria you can evaluate in code.
Lua API reference
The Script Engine exposes several objects and functions. This section covers the most commonly used ones — grouped by what they do.
Session properties
These are the core functions for reading and writing data on the call session.
Function | Type | Description |
|---|---|---|
| Resume | Sets a custom session property. When |
| Resume | Reads a session property. Returns the value or |
| Resume | Modifies a platform behaviour — not a custom property. Used for phone events, call queue priority, and call queue weight. |
| Resume | Expands all |
ℹ️
session.set() vs session.modify(): Use
session.set()when you want to store a custom value (CTIText, a department name, a flag). Usesession.modify()only for the specific platform behaviours it supports — primarilyMISSED_CALL_EVENTS,MISSED_CALL_LOGS,CALL_QUEUE_PRIORITY, andCALL_QUEUE_WEIGHT.
Voice and caller interaction
Functions for speaking to the caller and collecting input.
Function | Type | Description |
|---|---|---|
| Resume | Speaks TTS to the caller. Blocks until the phrase finishes playing. |
| Resume | Plays a prompt, then collects DTMF digits from the caller. Validates input against regex patterns you define. Returns a table with the matched value, or |
| Resume | Answers the call channel (required before TTS or IVR if the call hasn't been answered yet). |
| Resume | Requests a skill on the call for Call Queue skills-based routing. Agents without this skill (or outside the proficiency range) may be deprioritised or excluded. |
Call control
Functions that connect, transfer, or end calls. These are Execute and Terminate — the script exits when they run.
Function | Type | Description |
|---|---|---|
| Terminate | Dials one or more numbers (serial or parallel) and connects the first to answer. Supports dialling by number, user ID, group ID, or DDI user. The script exits immediately. |
| Terminate | Ends the call with a specified reason code (e.g. |
| Terminate | Transfers the call to another component in the policy (referenced by extension hook name). The script exits immediately. |
Salesforce connectors
Query and update Salesforce directly from your script. The connectorId variable is pre-populated in the script boilerplate — use it directly in these functions.
Function | Type | Description |
|---|---|---|
| Resume | Runs a SOQL query against Salesforce. Returns a table of results and stores them in the named result set (accessible as |
| Resume | Creates a new record (when |
| Resume | Makes an HTTP GET or POST request to an external API. Returns the response as a table. |
Queue methods (skills routing)
These functions are only available inside a PreCallQueueHook script — a special script that runs just before a call enters the queue. They give you full control over agent selection order. This is configurable inside the "Call Queue App" once skills routing is enabled and LUA is selected.
Function | Type | Description |
|---|---|---|
| Resume | Returns a table of all agents in the queue group with their skills and proficiency levels. |
| Resume | Sets the preference order and time delays for agents. Higher preference = selected first. A time delay means the agent only activates after that many seconds at head of queue. |
| Resume | Returns all skills defined in your organisation (name, ID, default proficiency). |
Utility functions
Helper functions for time checks, JSON handling, debugging, and code reuse.
Function | Type | Description |
|---|---|---|
| Resume | Returns |
| Resume | Returns |
| Resume | Parses a JSON string into a Lua table. |
| Resume | Converts a Lua table into a JSON string. |
| Resume | Pauses script execution. Maximum 30,000 ms (30 seconds). |
| Resume | Loads a reusable Lua chunk from your organisation's Hooks library. Allows shared functions across multiple policies. |
| Resume | Returns a 3-letter ISO country code for an E.164 phone number. |
| Resume | Returns the error message from the last failed operation (e.g. a failed Salesforce query). |
Debugging your scripts
When a script doesn't behave as expected, use the built-in debug system to inspect what's happening at runtime.
Setting up debugging
In the Script Engine component configuration, set the Email Notification field to an email address where you want to receive debug output.
Add
enable_debug("email")at the top of your script.In the App at the bottom you will see a section to populate your email address. Ensure both the debug line and email are populated.
Use
print()to output values you want to inspect. For tables, usetprint()to pretty-print all keys and values.When the script finishes executing, the debug output is sent to your configured email address.
enable_debug("email")
local callerNumber = session.get("E164CallerNumber") or "unknown"
print("Caller number is: " .. callerNumber)
local sfResult = connector.sf_query(connectorId, "SELECT Id, Name FROM Contact WHERE Phone LIKE '%" .. callerNumber .. "%'", "Current")
if sfResult == false then
print("SF query failed: " .. last_error())
else
print("SF returned " .. table.getn(sfResult) .. " records")
tprint(sfResult)
end💡
Tip: Debug emails are only sent when the script completes. If a script hits an error and exits prematurely, any
print()output up to that point is still included in the email. Errors are always sent to the notification target regardless of whetherenable_debug()is enabled.
Examples
CTIText whisper
Display a custom message on the agent's CTI softphone. The CTIText property is visible for the duration of the call and wrap-up.
A simple static message:
session.set("CTIText", "Autumn Sales promotion caller", true)A dynamic message combining multiple Salesforce macro values that were collected previously on the policy. (Update the $(SForce_Current.Name) to the exact macro name you are wanting to use)
local callerName = session.expand_macro("$(SForce_Current.Name)") or "Unknown"
local accountName = session.expand_macro("$(SForce_Current.Account.Name)") or "Unknown"
session.set("CTIText", "Caller: " .. callerName .. " | Account: " .. accountName, true)💡
Tip: You can also set CTIText using the Manage Properties app without writing code. The Script Engine is preferable when you need to concatenate multiple values, apply conditional logic, or format the text dynamically.
Collecting caller input (IVR)
Use session.ivr() to collect multi-digit input from the caller with regex validation. This example collects a 4–6 digit account number, retrying with a prompt if the caller doesn't enter valid input:
-- Define the expected input patterns
local ivr = {}
ivr[1] = {}
ivr[1].name = "AccountNumber"
ivr[1].pattern = "^([0-9]{4,6})#$" -- 4-6 digits followed by #
ivr[2] = {}
ivr[2].name = "NoInput"
ivr[2].pattern = "(#)" -- Just # means they don't know it
-- Prompt the caller and retry if no valid input received
local res = nil
while res == nil do
res = session.ivr(ivr, "Please enter your account number followed by the hash key. If you do not know your account number, press hash.", nil, 7000, "accountIVR")
if res == nil then
session.say("Sorry, I didn't recognise that input.")
end
end
-- Use the result
if res["name"] == "AccountNumber" then
session.set("AccountNumber", res["value"], true)
return true -- Trigger connector to route to "known account" flow
else
return false -- Fall through to "unknown account" flow
endQuerying Salesforce
Look up a Salesforce record by caller number and store the result for use in downstream apps:
enable_debug("email")
local callerNumber = session.get("E164CallerNumber") or ""
-- Query Salesforce using the pre-populated connectorId
local result = connector.sf_query(connectorId, "SELECT Id, Name, Account.Name FROM Contact WHERE Phone LIKE '%" .. callerNumber .. "%'", "Caller")
if result == false then
print("Query failed: " .. last_error())
return false
end
-- Check if we found a match
local recordCount = table.getn(result)
if recordCount > 0 then
-- Result is now available as $(SForce_Caller.Name), $(SForce_Caller.Account.Name) etc.
session.set("CTIText", "Known caller: " .. result[1].Name, true)
return true
else
session.set("CTIText", "Unknown caller", true)
return false
endTime-based routing
Route calls based on the current time in a specific timezone — useful for custom business hours logic that goes beyond the Rule app:
-- Check if current time is within UK business hours
local isBusinessHours = time_between("08:30:00", "17:30:00", "Europe/London")
-- Check if today falls within a holiday closure
local isChristmas = date_between("2025-12-25 00:00:00", "2025-12-26 23:59:59", "Europe/London")
if isChristmas then
session.set("RouteTo", "holiday_closure", true)
return true
elseif isBusinessHours then
session.set("RouteTo", "open", true)
return false
else
session.set("RouteTo", "out_of_hours", true)
return true
endPhone Events (missed call reporting)
Enable Phone Event records in Salesforce to track each missed ring attempt. Place this script early in your inbound policy (before the Call Queue app):
session.modify("MISSED_CALL_EVENTS", "63")
session.modify("MISSED_CALL_LOGS", "63")The number 63 is a bit string that enables reporting for Call Queue + DDI + Group + User + Number + Unknown scenarios. See Enabling Phone Events for the full configuration guide and bit string reference.
Result
After the script executes, the call follows one of these paths:
Triggered (
return true) — the call routes out via the connector to the linked container.Not triggered (
return falseor no return) — the call passes to the next app in the container, or exits the container if this is the last app.
If the app is triggered but no connector is linked, the call is hung up by default.
ℹ️
Note: Any session properties set by the script are immediately available to all subsequent apps and macros in the policy — including Speak app messages, Query Object filters, and Notify app content.