Documentation Index

Fetch the complete documentation index at: https://docs.natterbox.com/llms.txt

Use this file to discover all available pages before exploring further.

Use SSO to log in and access the Help Center, where you can create and manage your support tickets to reduce your resolution times.

Script Engine App

Prev Next

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(), and session.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 with connector.sf_query() and connector.sf_store()).

  • nameSpacePrefix — the Natterbox managed package prefix (typically nbavs).

  • 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

return true

The app is triggered — the call routes out via the connector to the linked container.

return false or no return statement

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

session.set(key, value, visible)

Resume

Sets a custom session property. When visible is true, the property is exposed as a $(Custom_key) macro in downstream apps (Speak, Notify, etc.).

session.get(key)

Resume

Reads a session property. Returns the value or nil if not found. Use or '' to provide a default: session.get("myVar") or ""

session.modify(property, value)

Resume

Modifies a platform behaviour — not a custom property. Used for phone events, call queue priority, and call queue weight.

session.expand_macro(string)

Resume

Expands all $(macro) tokens in a string and returns the result. Use this to read Salesforce macros or built-in values within your script.

ℹ️

session.set() vs session.modify(): Use session.set() when you want to store a custom value (CTIText, a department name, a flag). Use session.modify() only for the specific platform behaviours it supports — primarily MISSED_CALL_EVENTS, MISSED_CALL_LOGS, CALL_QUEUE_PRIORITY, and CALL_QUEUE_WEIGHT.

Voice and caller interaction

Functions for speaking to the caller and collecting input.

Function

Type

Description

session.say(text)

Resume

Speaks TTS to the caller. Blocks until the phrase finishes playing.

session.ivr(pattern, phrase, voice, timeout, name)

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 nil if no valid input is received within the timeout.

session.answer()

Resume

Answers the call channel (required before TTS or IVR if the call hasn't been answered yet).

session.request_skill(name, min, max)

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

session.connect(dialString, flags)

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.

session.hangup(reason)

Terminate

Ends the call with a specified reason code (e.g. NORMAL_CLEARING, USER_BUSY). The script exits immediately.

session.dp_transfer(destination)

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

connector.sf_query(connectorId, soql, resultSet)

Resume

Runs a SOQL query against Salesforce. Returns a table of results and stores them in the named result set (accessible as $(SForce_resultSet.FieldName) macro).

connector.sf_store(connectorId, table, fields, ownerId, recordId, resultSet)

Resume

Creates a new record (when recordId is empty) or updates an existing record in Salesforce. Returns the record ID on success.

connector.http(url, method, user, password, timeout, resultSet, postData)

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

queue.get_group_skills()

Resume

Returns a table of all agents in the queue group with their skills and proficiency levels.

queue.set_group_order(agentTable)

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.

queue.get_skill_definitions()

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

time_between(start, end, timezone)

Resume

Returns true if the current time is between the start and end times (format: HH:MM:SS). Supports overnight ranges (e.g. 21:00 to 03:00).

date_between(start, end, timezone)

Resume

Returns true if the current date/time is between the start and end dates (format: YYYY-MM-DD HH:MM:SS).

json_decode(string)

Resume

Parses a JSON string into a Lua table.

json_encode(table)

Resume

Converts a Lua table into a JSON string.

sleep(milliseconds)

Resume

Pauses script execution. Maximum 30,000 ms (30 seconds).

loadfile('local://home/ScriptName')

Resume

Loads a reusable Lua chunk from your organisation's Hooks library. Allows shared functions across multiple policies.

resolve_country(phoneNumber)

Resume

Returns a 3-letter ISO country code for an E.164 phone number.

last_error()

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

  1. In the Script Engine component configuration, set the Email Notification field to an email address where you want to receive debug output.

  2. Add enable_debug("email") at the top of your script.

  3. 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.

  4. Use print() to output values you want to inspect. For tables, use tprint() to pretty-print all keys and values.

  5. 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 whether enable_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
end

Querying 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
end

Time-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
end

Phone 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 false or 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.