ServiceNow¶
ServiceNow works with pg_relay through the webhook transport, posting to the instance's Table API to create an incident (or any other task-type record — the same call against sc_request, problem, or a custom table differs only in the URL and field names). As with Jira, the point of a ticketing integration is the reference coming back: the created incident's number (INC0010042) is in the response body, classify_webhook_response returns it as the provider reference, and the queuing application reads it off the notification's status row — asynchronously, after the Processor's tick, correlated by the notifier pk.
One-time ServiceNow setup¶
- Create a dedicated integration user on the instance (a non-interactive service account). Grant it the roles your instance's ACLs require to insert into the target table — for
incidentthat is conventionallyitil, though hardened instances may use narrower scoped roles. - Decide the routing defaults: which assignment group new incidents land in, and the caller the tickets are filed under (integrations usually point
caller_idat the service account itself or a designated "monitoring" user). - ServiceNow's OAuth support is a token-exchange flow — a credential is swapped for a short-lived access token that must be refreshed. The webhook transport deliberately sends one uninterpreted request per event and cannot run that conversation (the same ruling as Squadcast's v3 API in Incident Alerting Platforms); that machinery exists only inside the email transports' OAuth2 support. Use Basic authentication with the integration user.
Export the password on the Processor host (see Keeping Secrets Out of the Database):
Profile and message¶
// profile
{
"url": "https://acme.service-now.com/api/now/table/incident?sysparm_fields=number,sys_id&sysparm_input_display_value=true",
"auth": {"style": "basic_auth", "username": "svc.pgrelay", "secret": "_env:SNOW_PASSWORD"},
"headers": {"Accept": "application/json"},
"timeout_seconds": 30
}
// message — Table API fields, sent verbatim
{
"short_description": "disk alert: volume is filling",
"description": "relay_dev tablespace at 91% and growing.",
"urgency": "2",
"impact": "2",
"assignment_group": "Database Operations",
"correlation_id": "pgrelay-33",
"correlation_display": "pg_relay"
}
Two query parameters on the URL do real work: sysparm_fields=number,sys_id trims the reply to just what the classifier needs (the default is the entire created record), and sysparm_input_display_value=true lets reference fields accept display values — "Database Operations" for assignment_group instead of that group's 32-character sys_id. Without the flag, every reference field must carry a sys_id.
The message is a flat object of the target table's column names:
| Ticket concept | message field |
Format and notes |
|---|---|---|
| Title | short_description |
Plain text — the incident list's headline column |
| Body | description |
Plain text, multi-line |
| Severity | urgency, impact |
"1"–"3" (high → low). ServiceNow derives priority from the urgency × impact matrix — never set priority directly; it is calculated and read-only by default |
| Caller | caller_id |
Reference to sys_user — display value (the user's name) under the flag, else a sys_id |
| Assignment group | assignment_group |
Reference to sys_user_group — display value or sys_id |
| Assignee | assigned_to |
Reference to sys_user; usually left for the group's own triage |
| Category | category, subcategory |
Choice-list values exactly as configured on the incident form |
| Affected CI | cmdb_ci |
Reference into the CMDB — display value or sys_id |
| Dedup key | correlation_id |
Free text; the field made for external-system correlation — set it to pgrelay-{pk} |
| Dedup label | correlation_display |
Human-readable source-system name, e.g. pg_relay |
| First comment | comments |
A journal field — lands in the activity stream as a comment, not a column; work_notes is its internal-only sibling |
An unknown field name does not error — the Table API silently ignores columns that don't exist on the target table, so a typo'd field simply vanishes. Check the created record once when writing a new message shape.
Interpreting the response¶
201is success. Withsysparm_fieldsas above the body is{"result": {"number": "INC0010042", "sys_id": "..."}}— returnresult.numberas the provider reference (the human-facing identifier; keepsys_idin the detail text if API-side follow-up matters).400asfailed— malformed body or a value a choice list rejects; the body'serror.message/error.detailmake the detail text.401(bad credentials) and403(the integration user's roles don't clear the table's ACLs) asfailed.404asfailed— a wrong instance host or table name in the URL.429, any5xx, andhttp_status = 0(no response) asretry.
One quirk worth knowing: a hibernating developer instance answers with a redirect to its wake-up page. The Processor never follows redirects and resolves any 3xx permanently — correct behaviour here (production instances don't hibernate), but it explains a redirect failure against a personal developer instance that "worked yesterday".
Duplicate tickets — better than Jira¶
The same at-least-once caveat applies: a crash between ServiceNow creating the incident and pg_relay's own commit re-sends the event, and the Table API has no built-in idempotency. But ServiceNow gives the mitigation a first-class home: correlation_id exists precisely so external systems can mark their records, and a small before-insert business rule on the incident table (abort or update-instead when an open incident with the same correlation_id exists) turns the marker into genuine dedup — a retried send then lands on the ticket it already created, the alerting-platform behaviour. The rule is ServiceNow-side configuration, not something pg_relay can impose; without it, correlation_id still makes duplicates trivially findable.
The Import Set alternative¶
Instance administrators who dislike direct table writes can point the profile at an Import Set instead: POST /api/now/import/{staging_table} with the same transport mechanics, where a transform map owns the mapping from the posted fields to real incident columns. It decouples pg_relay's message shape from the incident table (rename a column, adjust the map, no profile change) at the cost of an indirection: the immediate response references the staging row, and surfacing the resulting incident number back through classify_webhook_response depends on the transform running synchronously — verify that on your instance before relying on the reference.
Documentation-verified, not live-verified
Endpoints, auth mechanics, and response shapes in this chapter come from ServiceNow's current documentation, not from live sends through pg_relay. Create one real incident on a sub-production instance before relying on a profile in production.
This is the last chapter in this book. Return to How Notifications Work for the overview, or to Keeping Secrets Out of the Database for the secrets-handling rules that apply to every transport and provider in this book.