Skip to content
AscendCore
🔌 Integration Guide

ServiceNow Integration

Keep ServiceNow as your system of record. AscendCore becomes the approval-gated system of action on top: incidents flow in, a human approves in Slack or Teams, the runbook executes, and the decision is written back to the ticket. No migration.

In one sentence

A ServiceNow Business Rule POSTs new incidents to AscendCore, where the ticket is AI-classified into a runbook, held for explicit human approval, executed against your identity stack, and the outcome lands back on the originating ticket as a work note with an audit-chain reference.

How a ticket flows

  1. Ingest. Your Business Rule POSTs the incident to POST /api/v1/tickets/ingest with a scoped API key. One ticket is ingested at most once: retries and rule re-fires return the original run instead of posting a second card.
  2. Classify. AscendCore classifies the ticket text into a runbook intent with a confidence score. Low-confidence or unrecognized tickets are received, audit-logged, and left alone (no_action). Nothing executes on a guess.
  3. Approve.The matching runbook's approval card is posted to your IT admin channel (Slack, or Teams for Teams-channel orgs), with a threaded note showing the originating ticket number, a link back to it, and the AI's classification and confidence. A human approves or denies. Every runbook is approval-gated. There is no autonomous path.
  4. Execute.On approval, the runbook runs against your stack (Okta, Entra ID, M365) and the action is recorded on the org's tamper-evident SHA-256 audit chain.
  5. Write back. The decision (approved or denied, by whom, at what time) is appended to the originating ticket as a work note. Your ITSM keeps the complete story.

Step 1: Create a scoped API key

In your admin portal under API, create a key with the ticket:ingest scope. Least privilege applies: a key with only this scope can submit tickets but cannot execute runbooks directly, read audit rows, or browse the catalog.

Store the key in a ServiceNow system property (or a credential record), not in the Business Rule body.

Step 2: Add the Business Rule

Create an async Business Rule on the incident table, when: after insert, with two system properties (ascendcore.ingest_url, ascendcore.api_key) configured first:

(function executeRule(current /*GlideRecord: incident*/) {
  try {
    // System properties (set once under sys_properties.list):
    //   ascendcore.ingest_url  = https://ascendcore.ai/api/v1/tickets/ingest
    //   ascendcore.api_key     = ak_live_... (scope: ticket:ingest)
    var endpoint = gs.getProperty('ascendcore.ingest_url');
    var apiKey   = gs.getProperty('ascendcore.api_key');
    if (!endpoint || !apiKey) {
      gs.warn('[AscendCore] ingest skipped: properties not configured');
      return;
    }

    var instance = gs.getProperty('glide.servlet.uri');
    var body = {
      source: 'servicenow',
      ticket: {
        sysId: current.getUniqueValue(),
        number: current.getValue('number'),
        shortDescription: current.getValue('short_description'),
        description: current.getValue('description') || undefined,
        url: instance + 'nav_to.do?uri=incident.do?sys_id=' + current.getUniqueValue(),
        callerEmail: current.caller_id.email
          ? current.caller_id.email.toString()
          : undefined
      }
    };

    var r = new sn_ws.RESTMessageV2();
    r.setEndpoint(endpoint);
    r.setHttpMethod('POST');
    r.setRequestHeader('Authorization', 'Bearer ' + apiKey);
    r.setRequestHeader('Content-Type', 'application/json');
    r.setRequestBody(JSON.stringify(body));

    var resp = r.execute();
    gs.info('[AscendCore] ingest ' + current.getValue('number') +
            ' -> HTTP ' + resp.getStatusCode());
  } catch (e) {
    gs.error('[AscendCore] ingest failed: ' + e);
  }
})(current);

Flow Designer works equally well: use an outbound REST step with the same payload and headers. Scope the rule with a filter (for example, category = account access) if you only want a subset of incidents routed to AscendCore.

Payload reference

FieldRequiredPurpose
sourceYesMust be "servicenow" in this release.
ticket.sysIdYesIncident sys_id. Idempotency key and write-back target.
ticket.numberYesHuman-readable number (INC0010234). Shown to the approver.
ticket.shortDescriptionYesPrimary classification input.
ticket.descriptionNoAppended to the classification input when present.
ticket.urlNoDeep link back to the ticket, shown in the approval thread.
ticket.callerEmailNoTarget-user fallback when the ticket text names nobody else.

Supported intents in this release

IntentRunbookSystem
MFA resetRB-002Okta
Password resetRB-001Okta
Offboarding (suspend account)RB-011Okta
Account unlockRB-007Microsoft Entra ID
M365 license assignmentRB-006M365
Group add / remove (group named in the ticket)RB-013Microsoft Entra ID
New hire provisioning (name and email in the ticket)RB-003Okta + M365

Tickets that classify to anything else are acknowledged with no_action and audit-logged. Additional intents arrive as additive extensions; your Business Rule does not change.

What the write-back looks like

When the approval reaches a decision (from Slack, Teams, or the dashboard), AscendCore appends a work note to the originating incident:

[AscendCore] Runbook decision: APPROVED
Runbook: MFA Reset
Target user: Sarah Chen (sarah.chen@acme.com)
Decision by: slack:U0123APPROVER at 2026-06-09T17:42:11.310Z
Action executed by AscendCore after explicit human approval.
This decision is recorded on the org's tamper-evident audit chain in AscendCore.

Denied requests get a work note too, stating that no changes were made. The write-back is deliberately non-blocking: if your instance is unreachable, the approval flow itself is unaffected and the decision remains on the AscendCore audit chain.

Security model

Full API reference

The complete request/response contract for POST /api/v1/tickets/ingest (dispositions, error codes, schemas) lives in the OpenAPI reference. Using Jira instead? See the Jira guide.