Skip to main content

Documentation Index

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

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

About ServiceNow

ServiceNow is an IT service management (ITSM) platform that manages tickets, incidents, change requests, and IT assets.

What the ServiceNow integration enables

CapabilityDescription
Ticket SyncNative bidirectional sync between Serval tickets and ServiceNow incidents
Service CatalogSync and order ServiceNow catalog items directly through Serval workflows
Knowledge BaseSync knowledge articles from ServiceNow directly into Serval for AI-powered assistance
Workflow AutomationBuild Serval workflows for incidents, problems, changes, catalog ordering, and more using ServiceNow APIs
Access ProfilesSync ServiceNow groups, roles, departments, companies, and locations for access control and provisioning

ServiceNow Configuration

This integration requires a dedicated service account with specific, limited permissions. Do not use a personal account or admin account. Following the principle of least privilege ensures security and auditability.

Prerequisites

Before configuring the ServiceNow integration in Serval, ensure you have:
  • Access to your ServiceNow instance as an administrator
  • Permissions to create users, roles, and ACLs (and OAuth application records if using OAuth)
  • Knowledge of your ServiceNow instance name
  • A decision on authentication method (see below)
Serval supports two ways to authenticate to your instance’s REST APIs:
MethodWhat you enter in ServalNotes
OAuth 2.0 (Client Credentials)Instance name, Client ID, Client SecretRecommended. Serval obtains short-lived bearer tokens from https://<instance>.service-now.com/oauth_token.do.
Basic authenticationInstance name, Username, PasswordUses HTTP Basic auth with a ServiceNow user account. Use when inbound OAuth client credentials are not available or not yet configured.
Prefer OAuth 2.0 for production. Tokens expire and are refreshed automatically; you are not storing a long-lived user password in Serval for API access. Basic authentication remains supported for compatibility and simpler test setups.
You still create the same dedicated integration user and assign the same roles and ACLs in either case. For OAuth, that user is linked on the OAuth application (as the application user) so API calls run with the correct permissions.

Step 1: Create a Dedicated Integration User

1

Navigate to User Administration

  1. Log in to your ServiceNow instance as an administrator
  2. In the application navigator search bar, type “Users” and select User Administration → Users
  3. Click New to create a new user
2

Configure User Details

Fill in the required user information:
FieldValue
User IDserval.integration
First nameServal
Last nameIntegration
Active✅ Checked
Web service access only✅ Checked (recommended for API-only accounts)
Internal Integration User✅ Checked (if available in your ServiceNow version)
Photo (Optional)Download the Serval logo then select it from your downloads
Setting “Web service access only” ensures this account cannot be used for interactive login, improving security.
Click Submit to create the user.
3

Set the user password (Basic authentication only)

If you use OAuth 2.0, you typically do not need a password for Serval: API access is authorized via Client ID and Client Secret, and the integration user is designated on the OAuth application in the OAuth application setup section below. Follow your organization’s policies if a password is still required for the service account record.If you use Basic authentication, Serval needs this user’s password:
  1. On the Users page, search for the user you just created and click on it
  2. Click Set Password
  3. Click Generate Password
  4. Important: Click the Copy button to copy the password and save it securely
Save this password immediately — you will not be able to view it again. You will need it when you select Basic authentication in Serval.
  1. Click Submit

Step 2: Create a Custom Role for Serval

Creating a custom role with granular permissions is the recommended approach for production environments. This follows the principle of least privilege and provides better security than using out-of-the-box roles like itil or admin.
1

Create the Custom Role

  1. In the navigator, search for “Roles” and select User Administration → Roles
  2. Click New
  3. Fill in the role details:
FieldValue
Namex_serval_integration
DescriptionCustom role for Serval integration with least-privilege API access
  1. Click Submit
2

Add Base Roles

Open the role you just created and scroll down to the Contains Roles related list. Add the following base roles:
RolePurpose
rest_api_explorerEnables REST API access
personalize_choicesAllows reading sys_choice values
personalize_dictionaryAllows reading sys_dictionary for field definitions
These base roles provide the foundational REST API capabilities. We will add granular table access via ACLs in the next step.

Step 3: Configure Table Access via ACLs

Serval requires specific read/write access to various ServiceNow tables. You’ll create Access Control List (ACL) rules to grant only the necessary permissions.
Serval uses the following ServiceNow APIs, which may require specific plugins to be active on your instance:
  • Table API (/api/now/table) — Active by default on all instances
  • Service Catalog API (/api/sn_sc/servicecatalog) — Requires the Service Catalog plugin (active by default)
  • Change Management REST API (/api/sn_chg_rest/change) — Requires the Change Management - REST API plugin (com.snc.change_management.rest_api)
  • Attachment API (/api/now/attachment) — Active by default on all instances
The following tables require access:

Incident Management Tables

TableReadCreateWritePurpose
incidentIncident records (all standard fields)
sys_journal_fieldComments and work notes

Service Catalog Tables

TableReadCreateWritePurpose
sc_cat_itemCatalog item details
sc_categoryCatalog categories
sc_requestService requests
sc_req_itemRequested items
item_option_newCatalog item variable definitions
io_set_itemVariable set to catalog item mapping
question_choiceChoices for select-type catalog variables
sys_attachmentFile attachment uploads for requested items

Configuration Tables (Read-Only)

TableReadCreateWritePurpose
sys_userUser lookup for assignment
sys_user_groupAssignment group lookup
sys_choiceField dropdown values
sys_dictionaryField definitions
cmdb_ci_serviceBusiness services

Knowledge Base Tables (Read-Only)

TableReadCreateWritePurpose
kb_knowledge_baseKnowledge base metadata
kb_categoryKnowledge categories
kb_knowledgeKnowledge articles

User Criteria Tables (Read-Only)

These tables are used to determine which users have access to knowledge articles and catalog items.
TableReadCreateWritePurpose
user_criteriaUser criteria definitions (users, groups, companies, etc.)
kb_uc_can_read_mtomKnowledge article access - users who CAN read
kb_uc_cannot_read_mtomKnowledge article access - users who CANNOT read
sc_cat_item_user_criteria_mtomCatalog item access - users who CAN access
sc_cat_item_user_criteria_no_mtomCatalog item access - users who CANNOT access

Change Management Tables

These tables are required for change request workflows.
TableReadCreateWritePurpose
change_requestChange request records
sysapproval_approverChange request approval records
Creating change requests uses the Change Management REST API (/api/sn_chg_rest/change), which requires the Change Management - REST API plugin to be active on your ServiceNow instance. This plugin is active by default on most instances.

Problem Management Tables

TableReadCreateWritePurpose
problemProblem records

Access Management & Resource Sync Tables

These tables are required if using Serval’s access profile capabilities for syncing organizational resources and provisioning/deprovisioning users.
If you are not using access profiles, you can skip these ACLs. The sys_user and sys_user_group tables listed above under Configuration Tables still require read access for basic functionality regardless.
TableReadCreateWriteDeletePurpose
sys_userWrite access for department assignment (read access already listed above)
sys_user_grmemberGroup membership provisioning and deprovisioning
sys_user_roleRole definitions for resource sync
sys_user_has_roleRole assignment provisioning and deprovisioning
core_companyCompany records for resource sync
cmn_departmentDepartment records for resource sync
cmn_locationLocation records for resource sync
cmn_cost_centerCost center records for resource sync

Step 4: Assign the Custom Role to the Integration User

1

Open the User Record

  1. Navigate to User Administration → Users
  2. Find and open the serval.integration user
2

Add the Custom Role

  1. Scroll down to the Roles related list
  2. Click Edit
  3. Add the x_serval_integration role (or the OOB roles if you chose that approach)
  4. Click Save

Step 5: OAuth Application Setup (OAuth 2.0 only)

Skip this step if you use Basic authentication.
1

Enable inbound client credentials (system property)

Inbound OAuth client credentials are disabled by default on most ServiceNow instances. You must enable the feature before creating or using the OAuth application.
  1. In the navigator, type sys_properties.list and press Enter to open the System Properties table
  2. Search for glide.oauth.inbound.client.credential.grant_type.enabled
  3. If the property does not exist, click New and create it:
FieldValue
Nameglide.oauth.inbound.client.credential.grant_type.enabled
Type`truefalse`
Valuetrue
ApplicationGlobal
  1. If the property already exists but is set to false, change it to true
  2. Click Submit / Update
Without this property set to true, every client-credentials token request to oauth_token.do will fail with {"error":"server_error","error_description":"access_denied"} regardless of how the OAuth application is configured. ServiceNow may also display a banner on the Application Registry form warning that the property is disabled.
2

Create an OAuth application

  1. Log in as an administrator
  2. Open System OAuth → Application Registry
  3. Click New. ServiceNow shows a list of OAuth application types — pick the one for inbound access (external systems calling into your instance). Do not choose Connect to a third party OAuth Provider or other outbound options; those are for ServiceNow calling external OAuth servers, not for Serval.
  4. Recommended: New Inbound Integration Experience — current UI for inbound OAuth (including client credentials). Use this when it appears in the list. When the wizard asks Select your application connection type, choose OAuth - Client credentials grant (machine-to-machine; this matches how Serval calls oauth_token.do). Do not choose Authorization code, JWT bearer, Resource owner password, or third-party ID token flows for the Serval integration.
ServiceNow modal Select your application connection type showing five options; OAuth Client credentials grant is highlighted as the choice for Serval
  1. Alternative: [Deprecated UI] Create an OAuth API endpoint for external clients — older wizard for the same kind of inbound app Serval uses. “Deprecated UI” refers to ServiceNow replacing the screen flow, not to Serval rejecting the credentials. It can still work and still produces Client ID / Client Secret and oauth_token.do client-credentials tokens. Some teams see odd default forms (for example, OAuth Application User hidden until you change Form layout); trying New Inbound Integration Experience instead often avoids that.
  2. Complete the wizard or form and give the application a clear name (for example, Serval integration). Follow prompts to associate your integration user if the flow asks for it — wording depends on your release; align with ServiceNow’s inbound OAuth documentation if steps differ slightly.
3

Enable client credentials and link the integration user

  1. Set Client type to run API calls as your integration account — for example Integration as a User (wording varies by release). That tells ServiceNow which sys_user supplies roles, ACLs, and audit fields (sys_created_by / sys_updated_by) for requests made with a client-credentials token.
  2. Allow the Client credentials grant. Often this is not on the main form: open the OAuth Entity or OAuth Entity Profile related list on the same Application Registry record, open the related row, and confirm Client credentials is enabled. Save that record if you changed it.
  3. Set OAuth Application User to your integration user (for example serval.integration). Serval needs this so issued tokens act as that user. If you do not see an “OAuth Application User” (or similar) field on the form, ServiceNow frequently hides it on the default layout. Add it explicitly:
    • On the Application Registry record, open the header context menu (⋮) → ConfigureForm layout — or use Personalize / Form design if that is what your UI offers.
    • Move OAuth Application User from Available to Selected, save the layout, then reload the form. The field should appear so you can select your integration user.
    Other ways some instances expose the same value:
    • List view: Switch Application Registry to the list layout; OAuth Application User may appear as a column you can set inline (community-reported on newer releases).
    • Related profile: On a few releases, the user reference lives on the OAuth Entity Profile row — open it from the related list and look for an application or integration user field.
  4. Click Update / Save on the Application Registry record.
The integration user must already have the roles and ACLs from the earlier steps so token-backed API calls are authorized correctly.
4

Copy Client ID and Client Secret

  1. From the OAuth application record, copy the Client ID and Client Secret
  2. Store them securely; you will paste them into Serval when Authentication method is OAuth 2.0 (Client Credentials)
Anyone with the Client ID and Client Secret can request OAuth tokens for this application’s user context. Protect them like a password.
If client credentials for inbound OAuth are not available on your instance (plugins, release, or admin policy), use Basic authentication in Serval instead, or ask your ServiceNow administrator to enable inbound OAuth per ServiceNow’s documentation. Ensure you have completed the Enable inbound client credentials step above.
  • Ensure the user has First name and Last name populated. On some releases, users without both names are omitted from the inbound OAuth user picker.
  • Confirm the user is Active and matches any Identity type or integration-user rules your instance enforces.
  • If the field never appears even after form layout changes, try the list view on Application Registry or ask a ServiceNow admin to verify UI policies on the Application Registry / OAuth tables. ServiceNow also documents adding an OAuth application user under Platform securityAuthentication in the product docs (search for add OAuth application user in ServiceNow Docs).
Serval obtains tokens by posting client credentials to https://<instance>.service-now.com/oauth_token.do. If ServiceNow responds with {"error":"server_error","error_description":"access_denied"} (often surfaced in logs as oauth2: "server_error" "access_denied"), the token was rejected by ServiceNow, not by Serval’s storage. A log line that mentions decrypting the integration secret may still be wrapping this same OAuth failure.Work through these on the ServiceNow side:
  1. Inbound client credentials enabled — Verify you completed the Enable inbound client credentials step earlier in this guide. The system property glide.oauth.inbound.client.credential.grant_type.enabled must be true in Global scope. This is the most common cause of access_denied.
  2. OAuth application user — The inbound application must be tied to your integration user (for example via OAuth Application User on the Application Registry record, or the equivalent in the new inbound wizard). That user must be Active and have the roles/ACLs from this guide.
  3. Client credentials grant — On the OAuth Entity / OAuth Entity Profile related to the application, Client credentials must be allowed.
  4. Client ID and Client Secret — Copy again from ServiceNow into Serval with no leading/trailing spaces. If you rotated the secret in ServiceNow, update the connection in Serval.
  5. Auth Scopes (related list) — Serval requests a token without a scope parameter. If you added rows under Auth Scopes on the OAuth application, your instance may require a matching scope on the token request; try clearing optional scopes on the app so the default behavior matches Serval, or ask your ServiceNow admin.
Sanity check outside Serval: From a shell, POST the same grant ServiceNow documents. Use --data-urlencode so special characters in the client secret are percent-encoded correctly:
curl -s -X POST \
  'https://YOUR_INSTANCE.service-now.com/oauth_token.do' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'client_id=YOUR_CLIENT_ID' \
  --data-urlencode 'client_secret=YOUR_CLIENT_SECRET'
If this returns access_denied, fix the OAuth application configuration until you receive an access_token, then retry Serval.

Step 6: Identify Your ServiceNow Instance

Your ServiceNow instance name can be found in your ServiceNow URL:
  • If your ServiceNow URL is https://mycompany.service-now.com, then:
    • Instance Name: mycompany
The instance name is the subdomain portion of your ServiceNow URL.

Step 7: Configure the Integration in Serval

Once you have completed the ServiceNow setup (user, roles, ACLs, and OAuth application if applicable), connect Serval:
1

Navigate to ServiceNow App

  1. In Serval, go to Apps → Available → ServiceNow → Connect
  2. The ServiceNow configuration form will appear
2

Enter configuration details

Choose Authentication method to match how you set up ServiceNow, then complete the fields.
3

Test the Connection

  1. Navigate to the API Integration tab
  2. Click Run on the healthchecks to verify that Serval can successfully authenticate with your ServiceNow instance
Need additional help with your ServiceNow integration? Contact support@serval.com for technical assistance or advanced configuration questions.

Real-Time Comment Sync via Webhook (Optional)

By default, Serval polls your ServiceNow instance to sync comments. For faster, near-instant comment delivery, you can configure a Business Rule that pushes new comments to Serval via webhook. This applies to comments on Incidents and Catalog Request Items that were created by the Serval integration.
This requires a single Business Rule on the sys_journal_field table. No ServiceNow plugins, store apps, or additional licensing are required.

How it works

When a comment is added to a Serval-managed record, an async insert Business Rule fires on the sys_journal_field table. The rule verifies the parent record was opened by the Serval service account, constructs a JSON payload, and sends it asynchronously to the Serval webhook. Serval processes the event and delivers the comment to the relevant conversation in real time.

Webhook endpoint

PropertyValue
URLhttps://svwebhook.api.serval.com/servicenow/new-comment
MethodPOST
Content-Typeapplication/json
AuthenticationToken via X-Serval-Webhook-Token header
The Business Rule sends a JSON body with this structure:
{
  "instance_url":  "https://customer.service-now.com",
  "table":         "incident",
  "record_sys_id": "a1b2c3d4e5f6...",
  "number":        "INC0012345",
  "comment": {
    "sys_id":          "f6e5d4c3b2a1...",
    "element":         "comments",
    "value":           "The comment body text",
    "sys_created_by":  "jane.doe",
    "sys_created_on":  "2026-03-13 14:30:00"
  }
}
FieldTypeDescription
instance_urlstringYour ServiceNow instance URL
tablestringSource table: incident, sn_hr_core_case, or sc_req_item
record_sys_idstringsys_id of the parent record
numberstringRecord number, e.g. INC0012345, HRC0001234, RITM0056789
comment.sys_idstringsys_id of the journal entry
comment.elementstringcomments or work_notes
comment.valuestringThe comment body text
comment.sys_created_bystringuser_name of the commenter
comment.sys_created_onstringServiceNow timestamp

Create the Business Rule

1

Navigate to Business Rules

In ServiceNow, go to System Definition → Business Rules and click New.
2

Configure the rule settings

Set the following fields:
FieldValue
NameServal – Sync Comment to Webhook
Tablesys_journal_field
AdvancedChecked
Whenasync
InsertChecked
UpdateUnchecked
DeleteUnchecked
QueryUnchecked
ServiceNow Business Rule configuration showing the When to run tab with async timing, Insert checked, and the sys_journal_field table selected
3

Add the script

Switch to the Advanced tab and paste the following script:
ServiceNow Business Rule Advanced tab showing the webhook script in the script editor
(function executeRule(current, previous /*null when async*/) {

    var tableName   = current.getValue('name');
    var elementType = current.getValue('element');

    // Validate: only Serval-managed records.
    // Replace 'serval.integration' with the user_name of your
    // Serval service account if it differs.
    var SERVAL_USER = 'serval.integration';

    var recordSysId = current.getValue('element_id');
    var parentRecord = new GlideRecord(tableName);
    if (!parentRecord.get(recordSysId)) {
        gs.warn('Serval webhook: parent record not found: '
                + tableName + '/' + recordSysId);
        return;
    }

    var openedBy = parentRecord.opened_by.user_name.toString();
    if (openedBy !== SERVAL_USER) {
        return;
    }

    // Build payload
    var instanceName = gs.getProperty('instance_name');
    var instanceUrl  = 'https://' + instanceName + '.service-now.com';

    var payload = {
        instance_url:  instanceUrl,
        table:         tableName,
        record_sys_id: recordSysId,
        number:        parentRecord.getValue('number'),
        comment: {
            sys_id:         current.getUniqueValue(),
            element:        elementType,
            value:          current.getValue('value'),
            sys_created_by: current.getValue('sys_created_by'),
            sys_created_on: current.getValue('sys_created_on')
        }
    };

    // Configuration
    // Replace the token value with the token provided by Serval.
    // For production, store this in a System Property
    // (e.g., x_serval.webhook_token) and retrieve it via
    // gs.getProperty('x_serval.webhook_token').
    var SERVAL_WEBHOOK_URL   = 'https://svwebhook.api.serval.com/servicenow/new-comment';
    var SERVAL_WEBHOOK_TOKEN = '<TOKEN_PROVIDED_BY_SERVAL>';

    try {
        var request = new sn_ws.RESTMessageV2();
        request.setEndpoint(SERVAL_WEBHOOK_URL);
        request.setHttpMethod('POST');
        request.setRequestHeader('Content-Type', 'application/json');
        request.setRequestHeader('X-Serval-Webhook-Token', SERVAL_WEBHOOK_TOKEN);
        request.setRequestBody(JSON.stringify(payload));

        // Run asynchronously so we don't block the user's transaction
        request.setEccParameter('skip_sensor', 'true');

        var response = request.executeAsync();

        gs.info('Serval webhook: sent ' + elementType + ' event for '
            + tableName + '/' + parentRecord.getValue('number')
            + ' (async)');
    } catch (e) {
        gs.error('Serval webhook: failed to send event: ' + e.message);
    }

})(current, previous);
The script references serval.integration as the Serval service account user_name. This must match the user_name of the service account provisioned in your instance. If your team chose a different name during onboarding, update the SERVAL_USER variable accordingly.
4

Save the Business Rule

Click Submit to save the rule. Comment sync will begin working immediately for Serval-managed records.

Live Agent Handoff via VA Bot-to-Bot (Optional)

Serval can hand a conversation off to a human ServiceNow agent mid-flow, then bridge the agent’s replies back into the Serval ticket so the user keeps chatting in their original surface (Slack, Teams, web, etc.). This rides on ServiceNow’s native Virtual Agent Bot-to-Bot APIs — there is no Serval-shipped update set and no Scripted REST API to install on your instance. Setup on the SNOW side is roughly: install the Virtual Agent API plugin (if not already installed), grant the Serval service account a few additional permissions, create a Token Verification + Message Auth + Provider Application + Outbound REST Message pointing at a Serval-hosted webhook URL, and (optionally) enable the AWA routing for the chat channel you want to use for the handoff.
Live agent sessions appear on Serval tickets as an additional external ticket with subtype = "interaction" on the existing ServiceNow integration — they do not create a separate integration or channel type. The auto-created interaction record on the SNOW side is the same record type SNOW uses for all VA-mediated chats.

How the bridge works

The bridge uses three endpoints on SNOW’s side:
  1. POST /api/sn_va_as_service/bot/integration (SNOW inbound) — Serval calls this to open, send messages on, and end a Bot-to-Bot conversation. The action discriminator is in the body (action: "START_CONVERSATION" | "AGENT_CHAT" | "END_CONVERSATION").
  2. Outbound REST Message (SNOW outbound) — when the live agent types a reply, SNOW’s Provider Application looks up its associated Outbound REST Message and POSTs the response to the URL you configured there. That URL is the Serval-hosted webhook.
  3. /api/now/table/awa_agent_presence_capacity (SNOW inbound, optional) — Serval calls this from servicenow.checkLiveAgentAvailability as a pre-flight to know whether any agents are currently online.
Routing (which queue / which agent the conversation lands in) is handled entirely by AWA on the SNOW side. The two knobs you have from Serval workflow code are:
  • channelId on startLiveAgentSession — maps to a SNOW sys_cs_channel record. Defaults to "chat". The channel record’s “Bot to Bot Synchronous” flag and its AWA queue assignment together determine where the conversation routes.
  • contextVariables on startLiveAgentSession — a free-form key/value bag passed to SNOW as contextVariables on every turn. Customers configure their AWA routing rule against the variable name(s) of their choice (e.g. u_liveagent_optional_skills). See Step 5 below.

Prerequisites

  • Virtual Agent + Virtual Agent API plugins installed on your ServiceNow instance (Quebec or later).
  • The Serval ServiceNow integration must already be connected (Steps 1–7 above).
  • Now Assist Pro licensing is not required — Bot-to-Bot is part of the base Virtual Agent API plugin.

Limitations (read before you commit)

  • Closing a Serval ticket does not automatically end the SNOW conversation. Your workflow should call servicenow.endLiveAgentSession({...}) when the ticket resolves if you want symmetric cleanup. The conversation eventually times out on the SNOW side either way.
  • The agent’s identity is not in the live message stream. SNOW’s Bot-to-Bot response payloads carry the agent’s typed text (uiType: "OutputText") but not the human agent’s sys_user reference. If you need to render “Agent Jane Doe joined” in the user’s surface, poll the auto-created interaction record’s assigned_to field from a workflow — see the Optional: post-START agent-identity poll example below.
  • Inbound auth verification is currently advisory. The Basic-auth password on inbound webhooks is forwarded onto the event for downstream verification, but the constant-time check against the rotated token is not yet enforced server-side. The per-install webhook URL contains an unguessable UUID, which is the practical access control today. Treat the rotated token as a not-yet-load-bearing secret in the meantime — full server-side verification is on the roadmap.
  • Attachments do not yet cross the bridge. Files attached on either side are not synced through Bot-to-Bot. Plain text only.

Step 1: Grant additional permissions to the Serval service account

The standard Serval ACLs cover the Table API surface used by the rest of the integration. Live Agent needs two additional reads on AWA tables and the interaction table, plus two SNOW roles. Add these to your Serval service-account user: Roles:
RoleWhy
virtual_agent_adminRequired by the Bot-to-Bot endpoint (/api/sn_va_as_service/bot/integration) for the bot-management Scripted REST API to accept the call.
interaction_agentLets Serval read the auto-created interaction record after START_CONVERSATION (for the optional post-START agent-identity poll) and is required for any future attachment-transfer support.
Table reads (one ACL per table, Type: record, Operation: read, Requires role: x_serval_integration or whichever role you assigned to the Serval service account):
TablePurpose
awa_agent_presence_capacityUsed by checkLiveAgentAvailability. Without this, the action will return agentsAvailable: false for everyone.
awa_agent_presenceUsed by AWA routing — presence_capacity is a view that joins this in.
awa_agent_capacitySame; joined by the presence_capacity view.
interactionRead access for the optional assigned_to poll. Already covered if you’re using the interaction_agent role above.

Step 2: Generate the shared bot token in Serval

In your Serval workspace, navigate to App Instances → ServiceNow → Ticket Sync settings. Scroll to the Live Agent (VA Bot-to-Bot) section and click Generate Token. You’ll see three values you’ll need in the next step:
ValueWhat it is
Inbound Webhook URLServal-hosted URL the SNOW Outbound REST Message will POST agent replies to. Per-install — contains an unguessable UUID — so do not share between environments.
Authentication UsernameAlways the literal string serval.
Authentication TokenOne-time-displayed opaque secret — copy it now, you cannot view it again. Click Rotate Token later if you ever need a new one (rotation invalidates the previous value).
One token, two destinations on the SNOW side. This token value lives in two places on SNOW:
  • The Token Verification record. Serval injects this value into body.token on every /api/sn_va_as_service/bot/integration call; SNOW’s Message Auth middleware verifies it before processing.
  • The Outbound REST Message Basic-auth password. SNOW presents this value when posting agent replies to the Inbound Webhook URL above.
You’ll wire both up in the next step.

Step 3: Configure the four SNOW records that wire up Bot-to-Bot

SNOW’s Bot-to-Bot setup links four records together. The record names below are the OOB tables — the navigation labels may vary slightly by release.
1

Enable inbound auth on the VA Bot Integration Scripted REST API

Navigate to System Web Services → Scripted Web Services → Scripted REST APIs and open the VA Bot Integration record. Open the Bot Integration resource. On the Security tab, set Requires authentication = true and Requires ACL authorization = false. Save.This makes the bot integration endpoint reject anonymous calls — the Token Verification record you’ll create next is what gates it.
2

Create the Token Verification record

In the filter navigator, type token_verification.list and press Enter. Click New. Fill in:
FieldValue
NameServal (or any human-readable name)
TokenThe Authentication Token from Serval (from Step 2)
Save. This is the value SNOW will validate against body.token on every inbound call from Serval.
3

Create the Message Auth record

In the filter navigator, type message_auth.list and press Enter. Click New. Fill in:
FieldValue
NameServal Bot Auth
ProviderServal (free-text; only used for organization)
Inbound Message VerificationPick the Token Verification record from the previous step (Serval)
Outbound Message CreationPick the same Token Verification record
Save.
4

Create the Provider Application

In the filter navigator, type sys_cs_provider_application.list and press Enter. Click New. Fill in:
FieldValue
NameServal Bot
Inbound IDserval (or any unique slug — this is the appInboundId value, only needed in multi-bot instances; single-bot instances can pick any value)
Message AuthPick the Message Auth record from the previous step (Serval Bot Auth)
ProviderVA Bot to Bot Provider
Save. The name of this record matters for the next step.
5

Create the Outbound REST Message

In the filter navigator, type REST Message and open the table. Click New. Fill in:
FieldValue
NameMust exactly match the Provider Application name from the previous step (Serval Bot). SNOW finds the outbound endpoint by name lookup against the active Provider Application.
EndpointThe Inbound Webhook URL from Serval (Step 2).
Authentication typeBasic
Use mutual authenticationunchecked
Basic auth profileCreate a new Basic Auth profile (or pick an existing one) with Username: serval and Password: <Authentication Token from Step 2>.
Save. SNOW will now use this REST Message — looked up by name — whenever the Provider Application emits an agent reply.By convention, also create a single HTTP Method record on the REST Message named post with:
FieldValue
HTTP methodPOST
EndpointSame as the parent REST Message
HTTP HeadersContent-Type: application/json
Some SNOW releases auto-create this from the parent record; if your release does, you can skip the explicit creation.
6

Verify the chat channel is Bot-to-Bot enabled

In the filter navigator, type sys_cs_channel.list and press Enter. Find the channel you intend to route through (the OOB one is named chat — its sys_id is what you’ll pass as channelId from your workflows; the literal string "chat" also works on most releases).On the channel record, confirm Bot to Bot Synchronous = true. If it’s not, set it and save. Without this flag, the bot integration endpoint will reject START_CONVERSATION calls with a routing error.
If you ever click Rotate Token in Serval, you must update both SNOW records:
  1. The Token Verification record’s Token field.
  2. The Basic Auth profile used by the Outbound REST Message (or, if you embedded the password directly on the REST Message, update it there).
Live agent will fail in both directions until both values match the new token again.

Step 4: SDK building blocks

The SDK exposes four thin building-block actions. They are intentionally not orchestration helpers — live-agent flows vary enough between customer SNOW configurations (different chat experiences, custom AWA routing rules, renamed topics, varying trigger phrases for NLU) that workflow authors should compose these directly rather than configure a single high-level wrapper.
import { servicenow } from "@servalhq/sdk";

// Pre-flight: are any human agents online right now?
await servicenow.checkLiveAgentAvailability({}, ctx);
// → { agentsAvailable: boolean; queueDepth: number }

// Open a conversation. Caller supplies the message text (including any
// NLU trigger phrase), channelId, contextVariables for routing skills,
// and the user's SNOW sys_id.
await servicenow.startLiveAgentSession(
  {
    ticketId,
    user: { userId, email },
    message: "<full message text — see example workflow below>",
    channelId: "chat",
    contextVariables: { u_liveagent_optional_skills: "IT" },
    // connectTicket: true by default — wires the SNOW conversation onto
    // the Serval ticket as an `interaction`-subtype external ticket so
    // user replies auto-egress as AGENT_CHAT turns.
  },
  ctx,
);
// → { clientSessionId, requestId, clientMessageId, response }

// Manually send an AGENT_CHAT turn. Usually NOT needed — user replies
// on the Serval ticket auto-egress through the standard pipeline once
// the ticket is connected. Use this for programmatic messages
// (auto-replies, scripted status updates, testing).
await servicenow.sendLiveAgentMessage(
  { clientSessionId, user: { userId, email }, message, channelId, contextVariables },
  ctx,
);

// Force-end a session (e.g. on ticket resolve / user abandon / SLA timeout).
await servicenow.endLiveAgentSession(
  { clientSessionId, user: { userId, email }, reason: "ticket_resolved", channelId },
  ctx,
);

Step 5: Customize routing with context variables

contextVariables is the principal knob for AWA routing. The variable names you pass are customer-specific — they must match the conditions on whichever AWA routing rule you’ve configured on your SNOW instance. For example, GM’s “IT Live Agent Chat” queue is configured with the condition context.u_liveagent_optional_skills = IT. So a Serval workflow targeting that queue passes:
contextVariables: { u_liveagent_optional_skills: "IT" }
Other customers might use u_skill_required, routing_skill, or any other u_-prefixed (or unprefixed) custom variable name. To find the right variable name on your instance, check your AWA routing rule’s condition script, or ask your SNOW admin. Custom interaction-record fields (e.g. CMDB tag, custom assignment_group overrides, region routing) can also be projected on top of the auto-created interaction record via a SNOW-side Business Rule that reads our contextVariables payload. This is a pure SNOW-side customization — no Serval changes required.

Step 6: Example workflow — START_CONVERSATION with routing + agent-identity poll

This mirrors the canonical pattern verified end-to-end against a real ServiceNow instance. You can drop this into a Serval workflow as a starting point and adapt the routing skills, trigger phrase, and message composition to your environment.
import { workflow, sleep } from "serval/core";
import * as servicenow from "serval/integrations/servicenow";

export const main = workflow({
  fn: async function (
    args: {
      email: string;       // The end-user's email
      userMessage: string; // The user's natural-language message
      ticketId: string;    // The Serval ticket to bridge onto
    },
    ctx: servicenow.context.ServiceNowIntegration,
  ) {
    // 1) Resolve the SNOW sys_user.sys_id for this user. The Bot-to-Bot
    //    endpoint accepts email/username but account-linking works best
    //    with a sys_id, and AWA assignment is more reliable.
    const userSysId = await servicenow.lookupUserSysIdByEmail(
      { email: args.email },
      ctx,
    );

    // 2) Compose the message text. The trailing trigger phrase is what
    //    SNOW's NLU classifies as the "Live Agent Support" topic — the
    //    OOB topic that drives the AWA handoff without going through
    //    the Greetings picker. Customers who have renamed that topic
    //    or have a custom topic should adapt the phrase to match.
    const ROUTING_TRIGGER_PHRASE =
      "I need a live agent. Please connect me with Live Agent Support — I want to talk to an agent.";
    const message =
      args.userMessage && args.userMessage.length > 0
        ? args.userMessage + "\n\n" + ROUTING_TRIGGER_PHRASE
        : ROUTING_TRIGGER_PHRASE;

    // 3) Open the conversation. `connectTicket: true` (default) wires
    //    the SNOW clientSessionId onto the Serval ticket as an
    //    interaction-subtype external ticket, so subsequent user
    //    replies on the Serval ticket auto-egress as AGENT_CHAT turns.
    const { clientSessionId } = await servicenow.startLiveAgentSession(
      {
        ticketId: args.ticketId,
        user: { userId: userSysId, email: args.email },
        message,
        channelId: "chat",
        contextVariables: { u_liveagent_optional_skills: "IT" },
      },
      ctx,
    );

    // 4) Optional: poll the `interaction` table to learn which agent
    //    actually picked up the conversation. START returns only an
    //    async ack — the real assignment happens a moment later when
    //    AWA matches an available agent. Stop early once `assigned_to`
    //    is populated.
    let routingInteraction: Record<string, unknown> | null = null;
    for (let attempt = 0; attempt < 4; attempt++) {
      await sleep({ durationMs: 3000 });
      const { result } = await servicenow.tableApiRequest(
        {
          method: "GET",
          path: "/api/now/table/{tableName}",
          pathParams: { tableName: "interaction" },
          query: {
            sysparm_query:
              "opened_for=" + userSysId + "^type=chat^ORDERBYDESCsys_created_on",
            sysparm_fields:
              "sys_id,number,state,assigned_to,assignment_group,queue,opened_at",
            sysparm_display_value: "true",
            sysparm_exclude_reference_link: "true",
            sysparm_limit: "1",
          },
        },
        ctx,
      ) as { result: Array<Record<string, unknown>> };

      if (result.length > 0) {
        routingInteraction = result[0];
        if (routingInteraction["assigned_to"]) {
          break; // Agent picked up — done polling.
        }
      }
    }

    return {
      clientSessionId,
      assignedAgent: routingInteraction?.["assigned_to"] ?? null,
      assignmentGroup: routingInteraction?.["assignment_group"] ?? null,
    };
  },
});
While the session is active:
  • User → agent: any new message the user sends in their original surface fans out to the SNOW agent automatically as an AGENT_CHAT turn — no extra workflow code required (this is what servicenowInteractionEgresser on the Serval backend does).
  • Agent → user: SNOW’s Outbound REST Message posts the agent’s typed reply to the Inbound Webhook URL configured in Step 3. Serval ingresses it as a comment on the bridged ticket, which surfaces back in the user’s original surface.

Step 7: Optionally end the session symmetrically

When the Serval ticket auto-resolves or the user abandons:
import * as servicenow from "serval/integrations/servicenow";

await servicenow.endLiveAgentSession(
  {
    clientSessionId, // saved from the START workflow output
    user: { userId: userSysId, email: args.email },
    reason: "ticket_resolved",
    channelId: "chat",
    contextVariables: { u_liveagent_optional_skills: "IT" },
  },
  ctx,
);
This is best-effort — the SNOW side eventually times the conversation out either way, but an explicit END_CONVERSATION cleans up the interaction record faster and gives the agent a clean “user ended chat” signal.
Need additional help wiring live-agent handoff into your workflows? Contact support@serval.com — we can help with custom routing-rule design, multi-bot Provider Application setups, interaction-field Business Rules, and rollout strategy.