ServiceNow & Jira — Bidirectional Integration

sakshinitna
Giga Guru

Introduction — ServiceNow and Jira

ServiceNow:
ServiceNow is a platform used by IT teams to manage incidents, requests, and services in one place, helping organisations keep their operations running smoothly.

 

Jira:
Jira is a tool mainly used by development teams to track bugs, manage tasks, and plan their work using agile methods like Scrum 

 

 

Why integrate ServiceNow and Jira?
In many organisations, IT teams and development teams use different tools, which can create communication gaps. Integrating ServiceNow and Jira helps both teams stay aligned, share updates easily, and resolve issues faster without switching platforms.

 

 

Prerequisites — Login and Setup

ServiceNow — Personal Developer Instance (PDI)

To follow this guide, you need access to a ServiceNow instance. If you are using a PDI:

 

Jira — Free Cloud Account

  • Go to https://atlassian.net and sign up for free
  • Create a new Jira Software project (Team-managed)
  • Note your project KEY (e.g., KAN, SN, IT)
  • Generate an API token: Profile → Manage Account → Security → API Tokens
  • Copy and store the token securely — it is shown only once

 

 

                         ----——————Let’s get started with the ServiceNow–Jira integration.————-------

 

1. System Properties

Store your Jira credentials securely in System Properties rather than hardcoding them.

In service now, Go to System Properties > All Properties and create these one by one:

      Property Name

 

              Value

x_jira.instance_url

https://yourcompany.atlassian.net

x_jira.username

your-jira-login@email.com

x_jira.api_token

your-api-token-from-atlas s i an

x_jira.project_key

KAN (your Jira project key)

 

Using System Properties instead of hardcoded credentials means you can update credentials without touching scripts. It also keeps sensitive tokens out of source code — a security best practice.

 

 

2. Script Include — JiraIntegrationUtils

We use a Script Include to keep all Jira logic in one place instead of repeating it in multiple Business Rules, making it easier to maintain and update.

This script acts as a utility that handles Jira operations like creating tickets, updating status, adding comments, and managing API calls—serving as a reusable bridge between ServiceNow and Jira.

Go to System Definitions > Script Include and create one -

 

var JiraIntegrationUtils = Class.create();

JiraIntegrationUtils.prototype = {

 

    initialise: function() {

        this.instanceUrl = gs .getProperty('x_jira.instance_url');

        this.username    = g s.getProperty('x_jira.username');

        this.apiToken    = g s.getProperty('x_jira.api_token');

        this.projectKey  = g s.getProperty('x_jira.project_key');

 

        this.authHeader = 'Basic ' +

            GlideStringUtil.base64Encode(this.username + ':' + this.apiToken);

    },

 

    // ─────────────────────────────────────────

    // CREATE JIRA TICKET

    // ─────────────────────────────────────────

    createJiraTicket: function(incidentGR) {

 

        try {

 

            if (!incidentGR || !incidentGR.isValidRecord()) {

                gs.error('Invalid incident record');

                return null;

            }

 

            var endpoint = this.instanceUrl + '/rest/api/3/issue';

 

            // 🔹 Priority Mapping

            var priorityMap = {

                '1': 'Highest',

                '2': 'High',

                '3': 'Medium',

                '4': 'Low',

                '5': 'Lowest'

            };

 

            var jiraPriority = priorityMap[incidentGR.getValue('priority')] || 'Medium';

 

            // 🔹 Issue Type Mapping

            var issueTypeMap = {

                'hardware': 'Task',

                'software': 'Task',

                'network':  'Task',

                'inquiry':  'Task',

                'database': 'Task'

            };

 

            var issueType = issueTypeMap[incidentGR.getValue('category')] || 'Task';

 

            // 🔹 Data Extraction

            var incNumber   = incidentGR.getValue('number');

            var shortDesc   = incidentGR.getValue('short_description') || 'No short description';

            var description = incidentGR.getValue('description') || 'No description';

            var caller      = incidentGR.caller_id.getDisplayValue() || 'N/A';

            var category    = incidentGR.category.getDisplayValue() || 'N/A';

            var priority    = incidentGR.priority.getDisplayValue() || 'N/A';

 

            // 🔹 Payload (ADF format)

            var payload = {

                fields: {

                    project: { key: this.projectKey },

                    summary: '[' + incNumber + '] ' + shortDesc,

 

                    description: {

                        type: 'doc',

                        version: 1,

                        content: [

                            {

                                type: 'paragraph',

                                content: [{ type: 'text', text: 'ServiceNow Incident: ' + incNumber }]

                            },

                            {

                                type: 'paragraph',

                                content: [{ type: 'text', text: 'Caller: ' + caller }]

                            },

                            {

                                type: 'paragraph',

                                content: [{ type: 'text', text: 'Category: ' + category }]

                            },

                            {

                                type: 'paragraph',

                                content: [{ type: 'text', text: 'Priority: ' + priority }]

                            },

                            {

                                type: 'paragraph',

                                content: [{ type: 'text', text: 'Description: ' + description }]

                            }

                        ]

                    },

 

                    issuetype: { name: issueType },

                    priority:  { name: jiraPriority }

                }

            };

 

            // 🔹 REST Call

            var request = new s n _ w s. RESTMessageV2();

            request.setEndpoint(endpoint);

            request.setHttpMethod('POST');

            request.setRequestHeader('Authorisation', this.authHeader);

            request.setRequestHeader('Content-Type', 'application/json');

            request.setRequestHeader('Accept', 'application/json');

            request.setRequestBody(JSON.stri n gify(payload));

            request.setHttpTimeout(30000);

 

            var response   = request.execute();

            var statusCode = response.getStatusCode();

            var body       = response.getBody();

 

            gs.info('Jira Create Response [' + statusCode + ']: ' + body);

 

            if (statusCode == 201) {

                var parsed = JSON.parse(body);

                return parsed.key;

            }

 

            g s.error('Jira creation failed: ' + body);

            return null;

 

        } catch (e) {

            gs.error('createJiraTicket ERROR: ' + e.message);

            return null;

        }

    },

 

    // ─────────────────────────────────────────

    // GET TRANSITIONS

    // ─────────────────────────────────────────

    getTransitions: function(jiraKey) {

 

        try {

            var endpoint = this.instanceUrl + '/rest/api/3/issue/' + jiraKey + '/transitions';

 

            var request = new s n_ w s .RESTMessageV2();

            request.setEndpoint(endpoint);

            request.setHttpMethod('GET');

            request.setRequestHeader('Authorisation', this.authHeader);

            request.setRequestHeader('Accept', 'application/json');

 

            var response = request.execute();

 

            if (response.getStatusCode() == 200) {

                return JSON.parse(response.getBody()).transitions || [];

            }

 

            return [];

 

        } catch (e) {

            gs.error('getTransitions ERROR: ' + e.message);

            return [];

        }

    },

 

    // ─────────────────────────────────────────

    // UPDATE STATUS

    // ─────────────────────────────────────────

    updateJiraStatus: function(jiraKey, transitionName) {

 

        try {

 

            var transitions = this.getTransitions(jiraKey);

            var transitionId = null;

 

            for (var i = 0; i < transitions.length; i++) {

                if (transitions[i].name.toLowerCase() === transitionName.toLowerCase()) {

                    transitionId = transitions[i].id;

                    break;

                }

            }

 

            if (!transitionId) {

                gs.warn('Transition not found: ' + transitionName);

                return false;

            }

 

            var endpoint = this.instanceUrl + '/rest/api/3/issue/' + jiraKey + '/transitions';

 

            var payload = {

                transition: { id: transitionId }

            };

 

            var request = new s n_w s.RESTMessageV2();

            request.setEndpoint(endpoint);

            request.setHttpMethod('POST');

            request.setRequestHeader('Authorisation', this.authHeader);

            request.setRequestHeader('Content-Type', 'application/json');

            request.setRequestBody(JSON.s trin gify(payload));

 

            var response = request.execute();

 

            return response.getStatusCode() == 204;

 

        } catch (e) {

            gs.error('updateJiraStatus ERROR: ' + e.message);

            return false;

        }

    },

 

    // ─────────────────────────────────────────

    // ADD COMMENT

    // ─────────────────────────────────────────

    addComment: function(jiraKey, commentText) {

 

        try {

 

            var endpoint = this.instanceUrl + '/rest/api/3/issue/' + jiraKey + '/comment';

 

            var payload = {

                body: {

                    type: 'doc',

                    version: 1,

                    content: [{

                        type: 'paragraph',

                        content: [{ type: 'text', text: commentText }]

                    }]

                }

            };

 

            var request = new s n_ w s.RESTMessageV2();

            request.setEndpoint(endpoint);

            request.setHttpMethod('POST');

            request.setRequestHeader('Authorisation', this.authHeader);

            request.setRequestHeader('Content-Type', 'application/json');

            request.setRequestBody(JSON. str ing if y(payload));

 

            var response = request.execute();

 

            return response.getStatusCode() == 201;

 

        } catch (e) {

            gs.error('addComment ERROR: ' + e.message);

            return false;

        }

    },

 

    type: 'JiraIntegrationUtils'

};

 

3. Business Rules — Outbound Sync (ServiceNow → Jira)

i. Business Rule 1 — Create Jira Ticket on Incident Insert

Purpose: When a new incident is created in ServiceNow, this rule automatically creates a corresponding Jira ticket and stores the Jira ticket key (e.g., KAN-7) in the u_jira_ticket_key field on the incident.

Configuration:

Setting

Value

Table

incident

When

after

Insert

 Checked

Update

 Unchecked

Filter Condition

priority  = 1 or 2

Active

Yes

 

(function executeRule(current, previous) {

 

    try {

        // ─────────────────────────────────────────

        // GUARD 1: Skip if already linked to Jira

        // ─────────────────────────────────────────

        if (current.getValue('u_jira_ticket_key')) {

            gs.info('JIRA BR: Already linked, skipping. Key: ' +

                    current.getValue('u_jira_ticket_key'));

            return;

        }

 

        // ─────────────────────────────────────────

        // GUARD 2: Skip if created by web hook

        // (prevents infinite loop when Jira creates SN incident)

        // ─────────────────────────────────────────

        if (g s. getUserName() === 'system' ||

            g s.getUserName() === 'admin' &&

            current.getValue('category') === 'software' &&

            current.getValue('short_description').indexOf('[Jira') === 0) {

            gs.info('JIRA BR: Skipping web hook-created incident');

            return;

        }

 

        gs.info('JIRA BR Insert: Processing incident ' + current.number);

 

        // ─────────────────────────────────────────

        // CREATE JIRA TICKET

        // ─────────────────────────────────────────

        var jira    = new JiraIntegrationUtils();

        var jiraKey = jira.createJiraTicket(current);

 

        gs.info('JIRA BR Insert: Jira key returned = ' + jiraKey);

 

        if (!jiraKey) {

            g s.error('JIRA BR Insert: Failed to get Jira key for ' +

                     current.number);

            return;

        }

 

        // ─────────────────────────────────────────

        // SAVE JIRA KEY BACK TO INCIDENT

        // Use separate GlideRecord — NOT current.update()

        // ─────────────────────────────────────────

        var g r = new GlideRecord('incident');

        g r.addQuery('number', current.number);

        gr.query();

 

        if (g r.next()) {

            g r.setValue('u_jira_ticket_key', jiraKey);

            g r.setValue('work_notes',

                '🔗 Jira ticket auto-created: ' + jiraKey +

                '\nView in Jira: ' +

                g s.getProperty('x_jira.instance_url') +

                '/browse/' + jiraKey

            );

            gr.setWorkflow(false);

            gr.autoSysFields(false);

            var saved = g r.update();

 

            if (saved) {

                gs.info('JIRA BR Insert:  Saved ' + jiraKey +

                        ' to incident ' + current.number);

            } else {

                g s.error('JIRA BR Insert:  Failed to save Jira key to ' +

                         current.number);

            }

        } else {

            g s.error('JIRA BR Insert:  Could not find incident ' +

                     current.number + ' to update');

        }

 

    } catch(e) {

        gs.error('JIRA BR Insert EXCEPTION: ' + e.message);

    }

 

})(current, previous);

 

 

ii. Business Rule 2 — Sync State Change to Jira

Purpose: When an agent changes the state of an incident in ServiceNow (e.g., from New to Resolved), this BR automatically moves the corresponding Jira ticket to the matching status and adds a comment.

Configuration:

Setting

Value

Table

incident

When

after

Insert

 Unchecked

Update

 Checked

Condition

current.state.changes() && u_jira_ticket_key != empty

Active

Yes

 

(function executeRule(current, previous) {

 

    try {

        var jiraKey = current.getValue('u_jira_ticket_key');

 

        // Exit if no Jira ticket linked

        if (!jiraKey || jiraKey.trim() === '') {

            return;

        }

 

        // Exit if state didn't actually change

        if (!current.state.changes()) {

            return;

        }

 

        var newState  = current.getValue('state');

        var oldState  = previous.getValue('state');

 

        gs.info('JIRA BR Update: State changed from ' +

                oldState + ' to ' + newState +

                ' for ' + current.number);

 

        // Map ServiceNow state → Jira transition name

        // Adjust these transition names to match YOUR Jira project workflow

        var transitionMap = {

            '1': 'To Do',        // New

            '2': 'In Progress',  // In Progress

            '3': 'In Progress',  // On Hold → keep In Progress in Jira

            '4': 'In Progress',  // Awaiting User Info

            '5': 'In Progress',  // Awaiting Problem

            '6': 'Done',         // Resolved

            '7': 'Done'          // Closed

        };

 

        var transition = transitionMap[newState];

 

        if (!transition) {

            g s.warn('JIRA BR Update: No transition mapped for state ' +

                    newState);

            return;

        }

 

        var jira    = new JiraIntegrationUtils();

        var success = jira.updateJiraStatus(jiraKey, transition);

 

        if (success) {

            // Add comment to Jira about the state change

            var stateDisplay = current.state.getDisplayValue();

            jira.addComment(

                jiraKey,

                'ServiceNow incident ' + current.number +

                ' state changed to: ' + stateDisplay +

                '\nUpdated by: ' + g s.getUserDisplayName()

            );

 

            gs.info('JIRA BR Update: Successfully synced state to Jira ' +

                    jiraKey);

        }

 

    } catch(e) {

        g s.error('JIRA BR Update error for ' +

                 current.number + ': ' + e.message);

    }

 

})(current, previous);

 

 

 

 

4. Custom Field — u_jira_ticket_key

A custom field is added to the Incident table to store the linked Jira ticket key (e.g., KAN-7). This field is the bridge between the two systems — every lookup and sync operation uses this key to find the matching record on both sides.

  • Table: incident
  • Column name: u_jira_ticket_key
  • Type: String, Max length: 40

 

 

 

5. Scripted REST API — Inbound Sync (Jira → ServiceNow)

What is a Scripted REST API?
It’s a way in ServiceNow to create your own custom API endpoint where you control what happens when an external system (like Jira) sends a request.

 

Why use it instead of a standard API?
Because it lets you handle Jira’s web hook data in your own way — process events, map fields properly, avoid loops, and respond cleanly — instead of just exposing raw table data.

 

System Web Services > Scripted REST APIs > New

Field Value
NameJira Web hook Receiver
API IDjira_web hook
Active

Then add a Resource:

Field Value
NameReceive Jira Update
HTTP MethodPOST
Relative Path/update
Requires Authentication

sakshinitna_0-1778243925845.png

 

sakshinitna_1-1778243925846.png

 

 

 

 

 

6. How the Jira Web hook Works

Jira web hooks are configured in Jira Settings > System > Web hooks. When an issue event occurs in Jira, it sends an HTTP POST request to your ServiceNow endpoint with a JSON payload containing all issue details including the event type, issue key, current status, priority, summary, and description.

6.4 Endpoint Configuration

Setting Value
API NameJira Web hook Receiver
API IDjira_web hook
Resource NameReceive Jira Update
HTTP MethodPOST
Relative Path/update
Full Endpoint URLhttps://devXXXXXX.service-now.com/api/[namespace]/jira_webhook/update

 

 

 

Conclusion

The ServiceNow-Jira bidirectional integration demonstrated here is a production-grade solution that eliminates the operational gap between ITSM and development teams. By leveraging ServiceNow's native capabilities — Script Includes, Business Rules, and Scripted REST APIs — alongside Jira's web hook system, we have built a seamless sync without any third-party middleware.

Rather than two teams working in silos, both tools now function as a unified system — incidents flow automatically, statuses stay in sync, and every change is logged with a full audit trail.

 

0 REPLIES 0