ServiceNow
ServiceNow Integration
Pensar Console integrates with ServiceNow to file security findings as records on your own instance — incident or, where the Security Incident Response plugin is installed, sn_si_incident — and to keep their state in sync with the finding in Pensar.
Unlike Linear or Slack, there is no Pensar-wide ServiceNow application to approve. A ServiceNow OAuth client lives inside a single instance, in that instance’s Application Registry, so the client ID and secret can only be created by someone with admin access to your instance. Everything below that requires work in ServiceNow — registering the OAuth application and, for inbound status sync, writing a Business Rule — exists for that reason.
Records created by Pensar are attributed to whichever identity you connect with: the ServiceNow user who approves the OAuth authorization, or the service account behind basic authentication. Pensar inherits that user’s roles — it can only read and write what they can.
Prerequisites
Before connecting ServiceNow, ensure you have:
- Admin access to your ServiceNow instance — enough to open System OAuth → Application Registry, and (for inbound status sync) to create a Script Include and a Business Rule
- Admin or owner access to the Pensar Console workspace you want to connect
- Your instance URL, in the form
https://<instance>.service-now.com
Pensar accepts the instance URL as an origin only — https://acme.service-now.com, not https://acme.service-now.com/now/nav/ui. It must use HTTPS, and any path, query string or trailing slash is rejected rather than silently trimmed.
Connecting ServiceNow
Both options start the same way:
- Navigate to Settings → Integrations → Issue tracking in your Pensar Console workspace
- Find the Issue sync card, locate the ServiceNow row and click Manage
- Enter your instance URL, then choose an authentication method
Option A — OAuth 2.0 (recommended)
OAuth gives Pensar a short-lived access token that it refreshes on its own, and a token you can revoke from inside ServiceNow at any time. Prefer it over basic authentication.
1. Register the OAuth application in ServiceNow
- In your ServiceNow instance, navigate to All → System OAuth → Application Registry
- Click New
- Choose Create an OAuth API endpoint for external clients
- Fill in:
- Name:
Pensar(or your preferred name) - Client ID: leave the generated value
- Client Secret: leave the generated value, or set your own
- Redirect URL:
https://console.pensar.dev/api/auth/servicenow-callback
- Name:
- Click Submit, then reopen the record and copy the Client ID and Client Secret
If your workspace is on a Console host other than console.pensar.dev, substitute that host — the path /api/auth/servicenow-callback is the same. The redirect URL must match exactly, including the scheme; ServiceNow rejects the authorization request otherwise.
2. Connect from Pensar
- Back in the ServiceNow section of the Issue sync card, paste the Client ID and Client Secret alongside your instance URL and submit
- You’ll be redirected to your instance’s authorization screen (
/oauth_auth.do) - Sign in as the user Pensar should act as, and approve the request
- You’ll be redirected back to Pensar Console with ServiceNow connected
There is no scope list to review on that screen. A ServiceNow OAuth client has no scopes of its own — it inherits the roles of the user who approves it. Approve as an account that holds the roles Pensar needs (see Roles), and no more.
Pensar refreshes the access token automatically before it expires. Two ServiceNow settings on the Application Registry record govern how long that can continue:
Some instances are configured to rotate the refresh token on every use and some are not. Pensar handles both — it stores a rotated token when ServiceNow returns one and keeps the existing one when it doesn’t.
Option B — Basic authentication
Where OAuth is not an option, Pensar can authenticate as a dedicated ServiceNow service account.
- In ServiceNow, navigate to All → User Administration → Users and click New
- Create a dedicated account — for example
pensar.integration— and set a strong password - Check Web service access only so the account cannot sign in to the UI, and ensure multi-factor authentication is not enforced for it (basic authentication cannot satisfy an MFA challenge)
- Grant it the roles below
- Enter the username and password in Pensar’s ServiceNow setup form
Basic authentication stores a long-lived shared credential. Pensar encrypts it at rest, but there is no rotation and no revocation path from Pensar’s side: Disconnect clears Pensar’s copy of the credential, it does not disable the account. Changing the password in ServiceNow breaks the integration until you reconnect with the new one, and any leak has to be handled by deactivating the account in ServiceNow. Use OAuth unless something prevents it.
Roles
Whichever method you use, the connected identity needs to:
- Create and update records on the table you file into —
itilcoversincident; Security Incident Response tables such assn_si_incidentneed the correspondingsn_siroles (sn_si.basicat minimum, plus an analyst role to write) - Read
sys_db_object— Pensar verifies the credential against it and uses it to discover which filing tables exist on your instance - Read
sys_choice— to resolve your table’s ownstatechoices when mapping status - Read
sys_dictionary— to check whether the target table declaresclose_codeandclose_notesbefore writing them
The last three are dictionary tables that most authenticated ServiceNow users can already read. If your instance restricts them, Pensar will connect but the table picker will come up empty.
What Gets Synced
Once connected and enabled, Pensar inserts a record on the routed table for each matching finding, setting four fields:
Nothing else is set. Pensar does not populate assignment_group, caller_id, category or configuration_item — your own assignment rules and Business Rules run on the new record exactly as they would on any other insert.
The description field renders literally in ServiceNow, so Pensar writes it as plain text rather than the Markdown it sends to other trackers. It contains:
- The severity
- The finding’s description
- A Details block: the Pensar reference, the affected location, the line number(s), the branch, and the CWE classification (or
Not classified) - A link back to the finding in the Pensar Console
- A link to the related pull request, when there is one
Severity Mapping
ServiceNow derives priority from the impact/urgency pair through your instance’s priority lookup rules, so those two are what Pensar sets:
The priority column shows what the out-of-box priority matrix produces. If your instance customises the lookup rules, your priorities will differ — Pensar sets impact and urgency and lets ServiceNow decide the rest.
Opening a finding in ServiceNow
When a ServiceNow record already exists for a finding, the finding’s actions menu includes View in ServiceNow. Otherwise you can file one from the same menu with Open as ServiceNow record. That does not require auto-sync to be enabled, only that ServiceNow is connected and a table is selected (the workspace default, or a per-repository override). If a record is already linked, Pensar opens it instead of creating a duplicate.
Choosing the Table
Pensar files into one table per repository, with a workspace default plus optional per-repository overrides — the same model as Linear teams.
The picker offers the filing tables your instance actually has:
Pensar reads this from the instance rather than offering both blind, so an instance without Security Incident Response will only show Incident.
Default Table
- In the ServiceNow section, set the workspace-level Table
- This is the fallback Pensar files into for any repository without an override, and for findings that aren’t tied to a specific repository
Per-Repository Overrides
Each repository can override the default routing:
Select a table before enabling sync for a repository. Repositories without an override inherit the workspace default.
Because the table is part of the routing, two repositories can file into different tables — and Pensar remembers which table each record lives on, so status sync keeps working after you change the routing.
Status Mapping
incident and sn_si_incident number their state field differently, and most instances customise both. A hardcoded number would file into the wrong state silently, so Pensar does not use one. Instead it reads the active state choices for the table out of your instance and matches them on their label:
The order matters, and it is the order in the table: the dismissal patterns are tried first, so a state labelled Closed - Duplicate reads as a false positive rather than a fix. The first pattern that matches wins.
The same matcher runs in both directions, so an outbound transition and an inbound webhook can never disagree about what a label means.
If no active state on your table matches — a fully renamed state list, for example — Pensar logs a warning and leaves the record alone. It will never guess a numeric state. If a Pensar status isn’t reaching ServiceNow, check that your table has an active state whose label matches one of the patterns above, and rename or add one if not.
Closure Fields
When Pensar closes a record, it also writes close_code and close_notes — but only on tables that declare those columns, checked against the table and its parents before writing:
Two-Way Status Sync
Outbound sync (Pensar → ServiceNow) needs no setup: closing or reopening a finding in Pensar transitions the linked record.
Inbound sync (ServiceNow → Pensar) does. ServiceNow has no application-level webhook Pensar can subscribe to, so your instance has to tell Pensar when a state changes. That takes four steps: copy the endpoint and secret out of Pensar, store them as system properties, add a Script Include that signs the payload, and add a Business Rule that sends it.
Make sure Reflect ServiceNow status changes is enabled in the ServiceNow section of the Issue sync card — that toggle is what allows an inbound change to move a Pensar finding.
1. Copy the webhook URL and signing secret
The ServiceNow section of the Issue sync card shows both. The URL is specific to your workspace and looks like:
The signing secret is generated when you connect. Treat it like a password.
2. Store them as system properties
Keeping both values in sys_properties means rotating the secret is a one-field edit rather than a script change.
- In ServiceNow, navigate to All → System Properties → All Properties and click New
- Create:
- On
pensar.webhook.secret, set Read roles toadminand check Private so the value isn’t captured in update sets
Paste both values with no leading or trailing whitespace. A stray newline in the secret changes the signature and every delivery will be rejected.
3. Add the signing Script Include
Pensar authenticates the webhook with an HMAC-SHA256 signature over the raw request body, sent as a hex digest. ServiceNow’s built-in GlideDigest produces plain hashes rather than HMACs, and Packages.javax.crypto is blocked by the script sandbox on current releases, so the signature is computed in script.
Create a Script Include:
- Name:
PensarSignature - Application: Global
- Accessible from: All application scopes
- Client callable: unchecked
- Active: checked
4. Add the Business Rule
Create one Business Rule per table you file into:
- Name:
Pensar — notify on state change - Table: the table Pensar files into (
Incident [incident]orSecurity Incident [sn_si_incident]) - Advanced: checked
- When to run → When:
after, with Update checked - When to run → Advanced → Condition:
current.state.changes() - Active: checked
Setting When to after makes the user’s save wait on the HTTP round trip. If you’d rather keep the network call off the transaction, change When to async — the script above is unchanged, and previous is simply unused. If you create the Business Rule inside a scoped application rather than Global, call the Script Include as new global.PensarSignature().
Pensar looks the incoming state value up in your table’s own choice list, resolves its label, and runs it through the status mapping above. A change that resolves to the status the finding already has is a no-op, so Pensar’s own outbound transitions don’t bounce back as inbound ones.
Rotating the Secret
If you regenerate the signing secret in Pensar, every delivery signed with the old one is rejected. Update pensar.webhook.secret in ServiceNow to the new value at the same time — there is nothing to change in the Script Include or the Business Rule.
Disconnecting ServiceNow
To remove the ServiceNow integration:
- Navigate to Settings → Integrations → Issue tracking
- Find the ServiceNow row in the Issue sync card and click Manage
- Click Disconnect
Disconnecting clears Pensar’s stored credential and webhook secret and stops both directions of sync. It does not reach into your instance: existing records are left in place, and you should also deactivate the Business Rule, and revoke the OAuth token (System OAuth → Manage Tokens) or deactivate the service account, on the ServiceNow side.
Troubleshooting
Authentication Failed (401)
OAuth. The access token expired and could not be refreshed. Common causes:
- The Refresh Token Lifespan on the Application Registry record elapsed — reconnect from Pensar
- The client secret was regenerated in ServiceNow — reconnect with the new secret
- The token was revoked under System OAuth → Manage Tokens
- The approving user was deactivated or lost the roles Pensar needs
Basic authentication. The credential no longer works. Check that the password hasn’t been changed or expired, that the account isn’t locked out, and that MFA isn’t being enforced for it. Reconnect with the current password.
Either. Confirm the instance URL is still correct and that the instance isn’t hibernating — a paused developer instance rejects everything until it’s woken up.
No Table in the Picker
Pensar lists only the filing tables it can see on your instance.
- If Security Incident is missing, the Security Incident Response plugin isn’t installed — file into
incidentinstead - If the picker is empty, the connected identity can’t read
sys_db_object. Grant it read access, or connect as an account that has it - Reconnect after changing roles — the picker reads the instance with the credential you connected with
Issues Not Being Created
- Confirm a workspace-level Table is set, or that the repository has a table override
- Check that Sync to ServiceNow is enabled for the repository
- Verify the finding’s severity meets the repository’s Severity Filter
- Confirm the connected identity can insert on the target table (
itilforincident)
Status Changes Not Reflecting
If a state change in ServiceNow doesn’t move the Pensar finding, work down this list:
- Reflect ServiceNow status changes is turned off in the ServiceNow section — inbound sync is disabled while it is
- Signature mismatch. The webhook rejects any body whose
x-pensar-signaturedoesn’t verify. Check thatpensar.webhook.secretmatches the secret currently shown in Pensar (rotating one without the other is the usual cause), that neither value has stray whitespace, and that nothing in your Business Rule modifiesbodybetween signing andsetRequestBody - No matching state label. If the new state’s label matches none of the patterns in Status Mapping, Pensar logs it and deliberately leaves the finding alone. Rename the state, or add one whose label matches
- The Business Rule didn’t fire. Its condition is
current.state.changes(), so an update that doesn’t changestatesends nothing. Confirm the rule is Active, is on the right table, and check System Logs → Outbound HTTP Requests for the delivery - The record wasn’t filed by Pensar. Status sync only applies to records Pensar created and linked to a finding
- Outbound HTTP is blocked. If your instance restricts egress, the REST message never leaves — the
gs.errorlines in the Business Rule will show it in System Logs → All
If the reverse direction is the problem — a finding closed in Pensar leaving the record open — see the note in Status Mapping about tables whose state labels match nothing.
Need Help?
If you encounter issues setting up your ServiceNow integration, please contact our support team at team@pensar.dev.