Skip to content

ServiceNow connector

Built-in servicenow connector. Floh stays the ticket of record. A ServiceNow incident is created only when a catalog (or other) workflow includes an explicit connector step: type servicenow, command createWorkItem. There is no automatic create on every Floh ticket, and there is no inbound sync from ServiceNow in this story.

The table name is hardcoded to incident. Catalog request items (sc_req_item) are out of scope — Floh is intake; ServiceNow is the ops work tracker.

Connection fields

Field Required Notes
baseUrl yes Instance origin, e.g. https://devXXXXX.service-now.com. Optional /api/now suffix is stripped. No embedded credentials. Private hosts require the flag below.
username yes ServiceNow username used with the password for HTTP Basic.
password yes ServiceNow password. Encrypted at rest. Sent only as HTTP Basic. Never logged or returned in API errors.
allowPrivateNetworkEndpoint no Allow baseUrl to target localhost / RFC1918 / Docker-internal hosts.

Do not put a webhook secret on this connector. Inbound webhooks are out of scope — Phase 3 v1 is one-way and opt-in.

Commands

Command Purpose
test Probe the incident table (GET /api/now/table/incident?sysparm_limit=1).
createWorkItem Create (or adopt by correlation_id = Floh ticket UUID) an incident. This is the catalog path.

Catalog setup

  1. Create a built-in connector instance of type servicenow and fill origin / username / password. Use Test to confirm.
  2. On the catalog workflow, keep create_ticket so a Floh ticket is created.
  3. Add a Connector step after create_ticket:
  4. Connector: the servicenow instance
  5. Command: createWorkItem
  6. name: e.g. {{title}} (incident short_description)
  7. description: optional
  8. priority: optional (critical maps to urgency 1, high to 2, else 3)
  9. externalId: {{ticket.id}}
  10. ticketNumber: {{ticket.ticketNumber}} (stored as correlation_display FLOH-{n})

The engine overwrites interpolated externalId / ticketNumber from the reserved {{ticket.*}} snapshot before calling ServiceNow. Missing or non-UUID ticket.id fails the step with no outbound POST.

If a servicenow row already exists for that Floh ticket, the step completes with the stored sys_id / number and does not call ServiceNow again.

On success the step writes ticket_external_link (external_system = servicenow) including a snapshot URL ({origin}/incident.do?sys_id={sys_id}). Admin ticket detail lists every stored link. Email inbound and POST /api/tickets do not create incidents.

Local e2e (option A)

Use a ServiceNow Personal Developer Instance. Sign in regularly or it hibernates. There is no Docker Compose peer in this repo (ServiceNow cannot run locally).

What this story does not do

  • Automatic incident create for every Floh ticket
  • Configurable table name / sc_req_item
  • OAuth
  • Inbound ServiceNow webhooks / CSRF-exempt routes
  • Outbound status or comment sync
  • Assignee or attachments

Rollback

  • Migration down() restores the previous external_system CHECK (fails if any jira rows exist; servicenow was already a CHECK value).
  • Remove the ServiceNow connector step, or disable the connector instance, to stop new incident creates. Existing Floh tickets and stored snapshot URLs are unchanged.

Requester

Opened by stays the integration user. Floh also writes Requested by: {name} <{email}> into the description, next to the Floh ticket number, whenever the reserved snapshot has a name or an email.

When that email matches exactly one active sys_user, Floh sets Caller (caller_id) after the incident exists. Zero rows or two-or-more rows leave Caller unset. opened_by is never sent. Lookup emails are a literal address; any other shape, including ServiceNow dynamic-query syntax, stays on the description line and is not sent as sysparm_query.

The integration user needs read on sys_user and write on incident.caller_id. A failed lookup or caller update does not fail the create. Floh does not create a ServiceNow user and does not use a placeholder account.

Already-linked Floh tickets are not updated. A local ticket_external_link returns the stored incident and does not call ServiceNow.

Security

  • Outbound HTTP uses connectorHttpRequest only (auth: basic, no raw fetch()).
  • baseUrl is validated like Vault/S3 (validateConnectorEndpoint): http/https only, no userinfo, private nets require the flag.
  • The table path is hardcoded incident — never interpolated from caller input.
  • Structured logs carry connector id, ticket id, incident sys_id / number, and a requester reason — never email, display name, short_description, description, or the password.