ServiceNow¶
ServiceNow is an IT service management platform. The ServiceNow LimaCharlie Extension provides bidirectional mirroring between LimaCharlie Cases and ServiceNow incidents, plus a set of outbound incident-management actions for D&R rules and AI agents: incident lifecycle (create/update/get/search), work notes and comments, attachments, CMDB lookups, and a read-only Table API escape hatch.
The sync model is stateless on the LimaCharlie side:
- LC → ServiceNow:
mirror_caseidempotently upserts a ServiceNow incident from a LimaCharlie Case, anchored on the incident's standardcorrelation_id/correlation_displayfields. Repeated calls update the same incident. - ServiceNow → LC:
pull_incident_changesreturns incidents updated since a watermark, normalized for feeding back into Cases. Changes made by the integration's own user are excluded, breaking echo loops.
Setup¶
1. Create a ServiceNow integration user¶
Create a dedicated ServiceNow user (for example lc.integration) with permissions to read/create/update records on the incident table (including work_notes, comments, correlation_id, and correlation_display), attach files, and read whatever other tables you intend to query (cmdb_ci for lookup_ci, etc.). Using a dedicated user matters: its username is how the extension recognizes — and filters out — its own writes.
2. Choose an authentication mode¶
| Mode | auth_mode |
Credentials needed | Notes |
|---|---|---|---|
| HTTP Basic | basic |
username, password |
Simplest; credentials sent on every request. |
| OAuth password grant | oauth_password |
client_id, client_secret, username, password |
Token obtained then auto-refreshed. |
| OAuth client credentials | oauth_client_credentials |
client_id, client_secret |
True server-to-server. Requires the instance property glide.oauth.inbound.client.credential.grant_type.enabled to be true. |
For the OAuth modes, create an OAuth API endpoint client in ServiceNow (System OAuth → Application Registry).
3. Subscribe to the extension¶
Subscribe to ext-servicenow from the LimaCharlie Marketplace (Extensions → Add-Ons).
4. Store the secrets¶
In Secrets Manager, store the password and/or client secret (for example servicenow-password, servicenow-client-secret).
5. Configure the extension¶
In Extensions → ext-servicenow → Configuration, fill in:
| Field | Required | Value |
|---|---|---|
instance_url |
yes | ServiceNow instance base URL, e.g. https://acme.service-now.com. |
auth_mode |
no | basic (default), oauth_password, or oauth_client_credentials. |
username |
for basic / oauth_password |
The integration user's username. |
password |
for basic / oauth_password |
Reference to the stored secret, e.g. hive://secret/servicenow-password. |
client_id |
for OAuth modes | OAuth client ID. |
client_secret |
for OAuth modes | Reference to the stored secret, e.g. hive://secret/servicenow-client-secret. |
correlation_display |
no | Label stamped on mirrored incidents' correlation_display. Default LimaCharlie. Scopes both upserts and polling, so multiple integrations can coexist. |
integration_user |
no | The ServiceNow username the extension authenticates as. pull_incident_changes excludes changes by this user to break echo loops. |
close_code |
no | Incident close_code applied when mirroring a case into Resolved/Closed (data policies usually require one). Default Solved (Permanently). |
Actions¶
Incident lifecycle¶
create_incident¶
Create a new incident. Only short_description is required.
| Field | Type | Notes |
|---|---|---|
short_description |
string | Required. Incident subject line. |
description |
string | Incident body. |
state |
int | 1 New, 2 In Progress, 3 On Hold, 6 Resolved, 7 Closed, 8 Canceled. |
urgency / impact |
int | 1 High … 3 Low. |
priority |
int | Usually derived from urgency × impact; set to override. |
category |
string | Category. |
assignment_group |
string | Assignment group sys_id (display names are not resolved). |
assigned_to |
string | Assignee user sys_id. |
caller_id |
string | Caller user sys_id. |
correlation_id |
string | External correlation id (e.g. an LC case id). |
correlation_display |
string | External system label. |
extra |
object | Raw ServiceNow incident fields to merge — escape hatch for fields not modeled above. |
Returns the created record, including sys_id and number.
update_incident¶
Update an incident by sys_id. Accepts the same fields as create_incident (minus short_description being required), plus:
| Field | Type | Notes |
|---|---|---|
sys_id |
string | Required. Incident to update. |
work_note |
string | Internal (IT-only) work note to append. |
comment |
string | Customer-visible comment to append. |
get_incident¶
Fetch a single incident by sys_id or human-readable number (e.g. INC0010023). Optional fields (comma-separated sysparm_fields) and display_value (false / true / all).
search_incidents¶
Search incidents with a ServiceNow encoded query.
| Field | Type | Notes |
|---|---|---|
query |
string | Encoded query (sysparm_query), e.g. active=true^state=2^ORDERBYDESCsys_updated_on. |
fields |
string | Comma-separated fields to return. |
limit |
int | Default 50. |
offset |
int | Pagination offset. |
display_value |
enum | false / true / all. |
Returns { "count": N, "incidents": [...] }.
Notes & attachments¶
add_note¶
Append an internal work note and/or a customer-visible comment to a record. Journal fields append — they never overwrite.
| Field | Type | Notes |
|---|---|---|
sys_id |
string | Required. Record sys_id. |
table |
string | Table name. Default incident. |
note |
string | Internal (IT-only) work note. |
comment |
string | Customer-visible comment. |
add_attachment¶
Upload a file onto a record.
| Field | Type | Notes |
|---|---|---|
sys_id |
string | Required. Record sys_id. |
file_name |
string | Required. Attachment file name. |
table |
string | Default incident. |
content_type |
string | MIME type. Default application/octet-stream. |
content |
string | File content — text, or base64 when content_base64 is true. |
content_base64 |
bool | Set for binary content. |
Reads¶
query_table¶
Read-only Table API query against any table (incident, problem, change_request, cmdb_ci, sys_user, …) — the escape hatch for data the typed actions don't cover, and the way to resolve display names to the sys_ids the write actions expect. Takes table (required), query, fields, limit (default 50), offset, display_value. Returns { "count": N, "records": [...] }.
lookup_ci¶
Resolve a CMDB configuration item — bridges LC sensor hostnames to the ServiceNow CMDB. Takes name (LIKE match), or a custom query; optional class (default cmdb_ci) and limit (default 50). Returns { "count": N, "cis": [...] }.
Case mirroring¶
mirror_case¶
Idempotently upsert a ServiceNow incident from a LimaCharlie Case. The incident is matched on correlation_id = case_id (scoped to this integration's correlation_display), so repeated calls update the same incident.
| Field | Type | Notes |
|---|---|---|
case_id |
string | Required. LimaCharlie case id — stored as correlation_id. |
case_number |
int | LC case number, used in the incident subject (LimaCharlie Case #N: …). |
status |
enum | new, in_progress, resolved, closed. |
severity |
enum | critical, high, medium, low, info. |
classification |
string | Case classification (appended to the description). |
summary |
string | Case summary — becomes the incident subject (first line, truncated to 160 chars) and description. |
conclusion |
string | Case conclusion — appended to the description; used as close_notes on terminal states. |
tags |
list of string | Appended to the description. |
assignees |
list of string | Accepted, but not currently reflected on the incident. |
correlation_display |
string | Override the configured label for this mirror. |
sync_note |
string | Optional work note recording the sync on the incident. |
Mappings applied:
- Status → incident
state:new→ 1,in_progress→ 2,resolved→ 6,closed→ 7. Terminal states also setclose_code(from config) andclose_notes. - Severity →
urgency/impact:critical→ 1/1,high→ ½,medium→ 2/2,lowandinfo→ 3/3.
Returns { "created": bool, "sys_id": "...", "number": "...", "incident": {...} }.
pull_incident_changes¶
Return incidents belonging to this integration (matched on correlation_display) updated at or after a watermark, normalized for feeding back into LC Cases.
| Field | Type | Notes |
|---|---|---|
since |
string | ServiceNow datetime watermark (YYYY-MM-DD HH:MM:SS, UTC). Empty bootstraps from the most recent changes; pass the returned watermark back on the next call. |
limit |
int | Default 100. Keep it above the largest expected same-second burst of updates. |
include_own_changes |
bool | Disable the echo-loop guard (changes by integration_user are excluded by default). |
Returns { "count": N, "changes": [...], "watermark": "YYYY-MM-DD HH:MM:SS" }. Each change carries sys_id, number, case_id (from correlation_id), state, a normalized case_status (new / in_progress / resolved / closed — On Hold maps to in_progress, Canceled to closed), short_description, sys_updated_on, and sys_updated_by.
The watermark boundary is inclusive (≥ since) so records sharing the watermark's second are never missed; de-duplicate by sys_id when applying changes.
Wiring up the bidirectional sync¶
Both directions are driven from D&R rules — the extension holds no schedule of its own:
- LC → ServiceNow: a D&R rule on Case events calls
mirror_casewith the case fields, pushing changes as they happen. - ServiceNow → LC: a scheduled D&R rule periodically calls
pull_incident_changes, passing back the watermark from the previous run, and applies the returned changes to Cases.
Because pull_incident_changes excludes the integration user's own writes, the LC → SN → LC round trip does not re-import what the extension itself mirrored.
Detection & Response¶
Example response action that opens a ServiceNow incident for a detection:
- action: extension request
extension action: create_incident
extension name: ext-servicenow
extension request:
short_description: '{{ .cat }} - {{ .routing.hostname }}'
description: '{{ .event }}'
urgency: 2
impact: 2
correlation_display: '{{ "LimaCharlie" }}'
Wrap literal strings in
{{ "..." }}. Values underextension requestare evaluated as templates. A bare string without{{ }}is interpreted as a gjson path against the event and, if it doesn't resolve, the key is silently dropped from the payload.
extension request actions are fire-and-forget — the rule engine does not surface the response back into the rule's evaluation context, so the freshly-created incident sys_id is not available to a subsequent action in the same rule. Workflows that need to chain (create an incident, attach a file, add notes) belong in a Playbook or an AI agent, which can hold the sys_id between calls.
Notes¶
- Reference fields (
assignment_group,assigned_to,caller_id) take sys_ids, not display names — resolve names first withquery_table. - ServiceNow rate limiting (
429) is honored once per request with aRetry-Aftercap of 5 seconds; a persistent429surfaces to the caller. - ServiceNow can return HTTP
200with a{"status": "failure"}envelope when a business rule or data policy aborts the operation — the extension treats this as an error, not a success. correlation_idandcorrelation_displayvalues are sanitized (encoded-query delimiters stripped) on both the write and the lookup path, keeping upserts idempotent even for hostile values.- OAuth tokens are refreshed automatically; rotating a secret in Secrets Manager takes effect after the next surfaced
401evicts the cached client. - Errors are surfaced as
servicenow api <status> on <path>: <message>.