ServiceNow Sensor
What it ingests
Six tables from the ServiceNow Table API (/api/now/table/{table}):
| ServiceNow table | enabled_objects name | Ontology entity | Signal |
|---|---|---|---|
sys_user (active users) | users | Person | user |
kb_knowledge (published articles) | knowledge | CreativeWork | kb_article |
incident | incidents | Issue | incident |
problem | problems | Issue | problem |
change_request | changes | Issue, plus a Decision once approved or rejected | change_request |
sc_req_item | requests | Issue, plus a Decision once approved or rejected | request_item |
How the graph is wired:
- Tickets are
Issues named by their number (INC0010001,CHG0030001). That matches Jira's key-as-name, so an incident's link to its problem or change resolves against the sameIssuethat problem's or change's own event created. The short description lives on thesubjectproperty and the event title. - People. The assignee gets
ASSIGNED_TOthe ticket. The caller (incident), requester (change), or requested-for user (catalog item) getsOPENED, falling back to whoever opened the ticket. KB authors getAUTHOREDthe article. All of them, and everysys_userrow, resolve toPersonby email and name — the same identity contract as Zendesk and HubSpot — so a ServiceNow assignee lands on the samePersonas their Slack, Jira, and calendar activity. - Related tickets. An incident's
problem_idandrfc(change) becomeRELATED_TOedges between the twoIssues. - Decisions. When a change request's or requested item's
approvalfield reaches Approved or Rejected, the ticket also emits aDecision(namedCHG0030001 change approval) that the ticket isDECIDED_ON. It carriesoutcome,cab_recommendation,cab_date, anddecided_at. A change that is rejected and later re-approved updates the sameDecisionrather than adding a second one. - Assignment group, state, priority, impact, urgency, risk, type, category, and catalog item are properties on the
Issue, using the labels a ServiceNow user sees (In Progress,1 - Critical).
Each table is its own signal, so you can disable one stream from the source's bindings without disabling the source.
Read-only and poll-based.
Credential
References a servicenow_credentials credential by id. The secret is a JSON object holding exactly one of two pairs:
Basic auth — an integration user's user ID and password:
curl -sS -X POST https://your-corveil-host/api/credentials \
-H "Authorization: Bearer <admin>" -H "Content-Type: application/json" \
-d '{"type":"servicenow_credentials","name":"acme-servicenow","secret":"{\"username\":\"corveil.integration\",\"password\":\"<password>\"}"}'OAuth 2.0 client credentials — an Application Registry entry's client ID and secret:
curl -sS -X POST https://your-corveil-host/api/credentials \
-H "Authorization: Bearer <admin>" -H "Content-Type: application/json" \
-d '{"type":"servicenow_credentials","name":"acme-servicenow-oauth","secret":"{\"client_id\":\"<client id>\",\"client_secret\":\"<client secret>\"}"}'With OAuth, Corveil mints a token at {instance_url}/oauth_token.do (grant_type=client_credentials), reuses it until shortly before it expires, and mints a new one if the instance rejects it early. Switching between Basic and OAuth is a credential rotation; the sensor does not change.
The vault rejects the common paste mistakes when you save: a bare password, both pairs at once, half a pair, an instance URL or user:password in username, and a Basic … header in password.
Config fields
| Key | Required | Notes |
|---|---|---|
instance_url | yes | Base URL of the instance: https://acme.service-now.com, a government instance such as https://acme.servicenowservices.com, or a customer-hosted URL. Must be https://. A bare host is accepted, and a pasted page link (…/now/nav/ui/home, …/navpage.do) or REST API Explorer URL (…/api/now/table/incident) is trimmed to the instance root, keeping any context path before it. |
enabled_objects | no | Comma-separated object kinds. Blank polls every supported kind. Table names work too (incident, change_request). Unknown names are rejected at configure time. |
Supported object names: users, knowledge, incidents, problems, changes, requests.
There is no default_entity_type. This source emits several primary types, so a single override would mistype the rest.
Any host is allowed in instance_url, because government and self-hosted instances live on their own domains. Corveil's sensor network guard still applies: on Corveil Cloud, an instance URL that resolves to a private or loopback address is refused when the sensor connects.
Provider-side setup
1. Create an integration user
In ServiceNow, go to User Administration → Users and click New.
Give it a user ID such as
corveil.integration. Check Web service access only, so the account cannot sign in to the UI.Set Time zone to GMT. See Known limits.
Grant read-only roles that cover the tables you enable:
Tables Role incident,problem,change_request,sc_req_itemitil(or a custom role with read ACLs on those tables)kb_knowledgeknowledgesys_userAny role with read on sys_user. Most instances grant this to all users.Corveil never writes. If your security team prefers least privilege over
itil, create a role with read ACLs on just these tables and the reference fields the sensor reads (assignee, caller, opened-by, requested-by and requested-for name and email, assignment group name).Set a password. If you use Basic auth, store this user ID and password as the credential.
2. (Optional) Use OAuth instead of a password
The client credentials grant needs a ServiceNow release that supports it and has it enabled:
- Go to System OAuth → Application Registry → New → Create an OAuth API endpoint for external clients.
- Name it
Corveil. Copy the generated Client ID and Client Secret. - Enable the client credentials grant for the application, and set its OAuth Application User to the integration user from step 1. ServiceNow runs every request as that user, so its roles decide what the sensor can read.
- If your instance gates the grant behind a system property, an admin sets
glide.oauth.inbound.client.credential.grant_type.enabledtotrue. - Store the client ID and secret as the credential.
3. Rate limits
ServiceNow has no fixed public API budget; administrators set inbound REST limits under System Web Services → Rate Limiting. If your instance has a rule that covers all users, add one for the integration user that allows at least a few requests per second.
Wire-up
POST /api/sensors
{
"sensor_type": "servicenow",
"name": "Acme ServiceNow",
"config": {
"instance_url": "https://acme.service-now.com",
"enabled_objects": "users, knowledge, incidents, problems, changes, requests"
},
"credentials": { "credentials": "<servicenow_credentials credential id>" }
}Verify
Run Test connection. It reads one row from every enabled table, so a missing role or ACL is reported against the table it blocks (cannot read the kb_knowledge table) at save time, not on the first poll. A 401 or 403 is reported as an authentication failure.
Once polling, each object kind's count appears in the per-poll summary log (incidents_collected, changes_collected, knowledge_collected, …). A kind that stays at zero while the others move usually means the integration user can't read that table.
In the ontology, search for a ticket number (INC0010001). It should be an Issue with ASSIGNED_TO and OPENED edges to the right people.
How incremental sync works
- The cadence is the worker's poll interval. Every poll walks each enabled table forward from where the last one stopped.
- The first poll records a starting point and emits nothing. History comes from the worker's one-time Backfill (
SENSOR_BACKFILL_SINCE, default 72h), so connecting a large instance doesn't send its whole history on the first poll. An object kind you add toenabled_objectslater starts the same way. - The cursor is
(sys_updated_on, sys_id)per table. Each query issys_updated_on > T, ordered bysys_updated_onthensys_id. Timestamps have one-second resolution, so a bulk update can stamp thousands of rows with one value. When a page fills inside one second, the next query reads the rest of that second bysys_idbefore moving on. No offsets are used, so rows changing while a poll runs don't shift pages and cause skips. - Only rows older than one minute are read. ServiceNow sets
sys_updated_onwhen a transaction starts, not when it commits. Holding back a minute stops the cursor from moving past a row that commits late. - Edits arrive as new events and update the same entity. The event id includes
sys_mod_count, so an edited ticket is ingested again. The entity id (servicenow:<host>:<table>:<sys_id>) stays the same, so the existingIssueis updated rather than duplicated. Including the host keeps two instances in one org from colliding. - Paging. 200 rows per request, up to 10 pages per table per poll. A busy table's remaining rows are picked up on the next poll.
- Rate limits. A 429 or 503 is retried up to three times, honoring
Retry-After(capped at 30s), then 1s/2s/4s. If it still fails, the poll stops with its cursor where it was, and the next poll tries again. - Free text is scrubbed. Descriptions, close notes, and KB bodies go through Corveil's secret redaction before they leave the sensor, so a pasted key or connection string never reaches the event store. KB article HTML is converted to plain text. Content is capped at 64 KiB per event.
Known limits
- Set the integration user's time zone to GMT. Stored values are UTC and the cursor is UTC, but some instances read date-times in a query in the session user's time zone. With a non-GMT integration user, the cursor can lag or lead by that offset.
- Work notes and additional comments are not ingested. They are journal fields stored in
sys_journal_field, not on the ticket row. Descriptions and close notes are ingested. - Approvals come from the ticket's own
approvalfield. Individual approver records (sysapproval_approver) are not read yet, so aDecisionrecords the outcome but not who approved it. - Only published KB articles and active users are read. Retiring an article or deactivating a user does not remove the existing entity.
- Deletes aren't detected. The Table API doesn't return deleted rows.
- No CMDB, catalog definitions, or custom tables in v1. Custom fields on the supported tables aren't read.
- Polling only. Business rules and outbound REST messages could push changes, but they need script changes on the instance. Polling needs only a read-only user.