Skip to content

ServiceNow Sensor ​

What it ingests ​

Six tables from the ServiceNow Table API (/api/now/table/{table}):

ServiceNow tableenabled_objects nameOntology entitySignal
sys_user (active users)usersPersonuser
kb_knowledge (published articles)knowledgeCreativeWorkkb_article
incidentincidentsIssueincident
problemproblemsIssueproblem
change_requestchangesIssue, plus a Decision once approved or rejectedchange_request
sc_req_itemrequestsIssue, plus a Decision once approved or rejectedrequest_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 same Issue that problem's or change's own event created. The short description lives on the subject property and the event title.
  • People. The assignee gets ASSIGNED_TO the ticket. The caller (incident), requester (change), or requested-for user (catalog item) gets OPENED, falling back to whoever opened the ticket. KB authors get AUTHORED the article. All of them, and every sys_user row, resolve to Person by email and name — the same identity contract as Zendesk and HubSpot — so a ServiceNow assignee lands on the same Person as their Slack, Jira, and calendar activity.
  • Related tickets. An incident's problem_id and rfc (change) become RELATED_TO edges between the two Issues.
  • Decisions. When a change request's or requested item's approval field reaches Approved or Rejected, the ticket also emits a Decision (named CHG0030001 change approval) that the ticket is DECIDED_ON. It carries outcome, cab_recommendation, cab_date, and decided_at. A change that is rejected and later re-approved updates the same Decision rather 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:

bash
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:

bash
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 ​

KeyRequiredNotes
instance_urlyesBase 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_objectsnoComma-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 ​

  1. In ServiceNow, go to User Administration → Users and click New.

  2. Give it a user ID such as corveil.integration. Check Web service access only, so the account cannot sign in to the UI.

  3. Set Time zone to GMT. See Known limits.

  4. Grant read-only roles that cover the tables you enable:

    TablesRole
    incident, problem, change_request, sc_req_itemitil (or a custom role with read ACLs on those tables)
    kb_knowledgeknowledge
    sys_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).

  5. 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:

  1. Go to System OAuth → Application Registry → New → Create an OAuth API endpoint for external clients.
  2. Name it Corveil. Copy the generated Client ID and Client Secret.
  3. 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.
  4. If your instance gates the grant behind a system property, an admin sets glide.oauth.inbound.client.credential.grant_type.enabled to true.
  5. 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

json
{
  "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 to enabled_objects later starts the same way.
  • The cursor is (sys_updated_on, sys_id) per table. Each query is sys_updated_on > T, ordered by sys_updated_on then sys_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 by sys_id before 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_on when 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 existing Issue is 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 approval field. Individual approver records (sysapproval_approver) are not read yet, so a Decision records 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.