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¶
- Create a built-in connector instance of type
servicenowand fill origin / username / password. Use Test to confirm. - On the catalog workflow, keep
create_ticketso a Floh ticket is created. - Add a Connector step after
create_ticket: - Connector: the
servicenowinstance - Command:
createWorkItem name: e.g.{{title}}(incidentshort_description)description: optionalpriority: optional (criticalmaps to urgency1,highto2, else3)externalId:{{ticket.id}}ticketNumber:{{ticket.ticketNumber}}(stored ascorrelation_displayFLOH-{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 previousexternal_systemCHECK (fails if anyjirarows exist;servicenowwas 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
connectorHttpRequestonly (auth: basic, no rawfetch()). baseUrlis 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 requesterreason— never email, display name, short_description, description, or the password.