This QuickStart introduces you to embedding Sigma into ServiceNow.
We will use a ServiceNow developer account and a Sigma trial to demonstrate how to minimally embed Sigma into a ServiceNow page using an iframe and a JSON Web Token (JWT) to secure the connection and provide the necessary JWT claims.
For more information on Sigma's product release strategy, see Sigma product releases.
If something doesn't work as expected, here's how to contact Sigma support.
The typical audience for this QuickStart includes users of Excel, common Business Intelligence or reporting tools, and semi-technical users who want to try out or learn Sigma.
We need to create a few things in Sigma to embed into our host application. We will keep this part simple, as it is not the primary focus of this QuickStart.
Client credentials (a unique Client ID and Embed Secret) are the foundation of secure embedding.
Sigma uses the Client ID to determine which Embed Secret is referenced in a request. Each time a request is made, the server-side embed API uses the Embed Secret to generate an encrypted signature.
Together, the Client ID and Embed Secret create a robust security framework for server-side interactions with Sigma.
Navigate to Administration
and scroll down to Developer Access
.
Click Create New
:
In the Create client credentials
modal, select both the REST API
and Embedding
checkboxes, provide a name, and assign an administrative user as the owner:
Click Create
.
Copy and paste the Client ID
and Secret
into a text file for later use.
Next, we'll create a team and share the workbook with that team. The host application will then pass Sigma the team information when accessing content, along with other user details.
Create a team named Embed_Users
(creating a workspace is not required):
Return to the homepage and click the + Create new
button, then select Workbook
:
Drag a new Table
from the Data
group on the Element bar
:
Click Select source
.
Sigma allows users to search for tables by name; type hands
in the search bar and select the PLUGS_ELECTRONICS_HANDS_ON_LAB_DATA
table from the RETAIL
schema:
This opens the selected table in a new, unsaved workbook named Exploration
:
Rename the table by double-clicking its name and changing it to Plugs_Sales_Transactions
.
Click the Save as
button and save the workbook as Embed_Service_QuickStart
.
Share both workbooks as shown below, setting the permission level to View
:
This will allow the View
user to look at the workbook and perform basic actions like sorting and filtering.
For more information on Sigma default account types, see Default account types.
Open the workbook's menu and select Go to published version
:
Copy the entire URL from the browser and paste into a text file for later use:
Navigating in ServiceNow can be challenging for those unfamiliar with its many features. For this reason, we will provide direct links for each configuration step. All links follow the same pattern:
<your_instance>: Replace this with the portion of the URL that identifies your ServiceNow developer instance.
For example, if the page URL is:
https://dev274798.service-now.com/sys_properties_list.do
use https://dev274798.service-now.com in place of <your_instance>.
If you prefer to use the ServiceNow navigation menu instead of direct links, that is also fine.
We will use ServiceNow System Properties
to configure required parameters (JWT claims) used when securely embedding Sigma.
For more information, see JSON web token claims reference
Log in to your ServiceNow developer instance. Once the instance is running (this can take a few minutes), navigate to:
<your_instance>.service-now.com/sys_properties_list.do
This process is repetitive, so we'll show the steps for the first property and then list the others.
Click New
Name the new system property x_sigma.client_id
, set the Type
to String
, and paste the Sigma Client ID
created earlier into the Value
box.
Click Submit
:
Repeat the same process for the remaining required system properties:
x_sigma.debug
– ‘true' or ‘false'.
x_sigma.secret
– Sigma embed secret created early.
x_sigma.base_url
– Sigma embed base URL from earlier steps
x_sigma.session_length
– session lifetime in seconds. Use 3600
.
x_sigma.account_type
- "View".
x_sigma.teams
- as created earlier, "Embed_Users".
x_sigma.email
- use "view.embed.qs@example.com" or another email address you prefer, as long as it is not an existing Sigma portal user.
Once created, we can see then all at once by setting a filter:
We need to create a Script Include that will handle the server-side generation of the Sigma embed JWT. This script will securely read the system properties we created earlier, construct the JWT claims, and sign them with the Sigma Embed Secret. The output will be a valid JWT that can be passed to the Sigma embed URL in our widget.
Navigate to:
<your_instance>.service-now.com/sys_script_include_list.do
Click New
Sigma_Embed_API
.All application scopes
.Active
to checked.Paste the following script into the script section (#4):
/**
* Script Include: Sigma_Embed_API
* Purpose: Generate a Sigma embed URL signed with HS256 (no platform crypto).
* Inputs via sys_properties:
* x_sigma.client_id (Sigma Embed Client ID) [required]
* x_sigma.secret (Sigma Embed Secret) [required]
* x_sigma.base_url (Workbook/Dashboard URL) [required]
* x_sigma.session_length (seconds, default 3600; hard-capped at 30d)
* x_sigma.email (default subject email if not passed)
* x_sigma.account_type (e.g., View)
* x_sigma.teams (comma-separated team names)
* x_sigma.ua_* (user_attributes map; keys are after ua_)
* x_sigma.eval_connection_id (optional)
* x_sigma.secret_is_base64 (true if secret is base64-encoded)
* x_sigma.debug (true to enable gs.info logs)
*/
var Sigma_Embed_API = Class.create();
Sigma_Embed_API.prototype = {
initialize: function () {
// Load core config from sys_properties
this.debug = (gs.getProperty('x_sigma.debug', 'false') === 'true');
this.client_id = gs.getProperty('x_sigma.client_id', '');
this.secret = gs.getProperty('x_sigma.secret', '');
this.base_url = gs.getProperty('x_sigma.base_url', '');
this.session_len = parseInt(gs.getProperty('x_sigma.session_length', '3600'), 10) || 3600;
// Hard fail early if required inputs are missing
if (!this.client_id || !this.secret || !this.base_url) {
throw 'Missing required properties: x_sigma.client_id, x_sigma.secret, x_sigma.base_url';
}
},
/**
* Build a signed Sigma embed URL.
* @param {string} email Optional subject email; falls back to x_sigma.email
* @returns {string} Full URL with :embed=true&:jwt=<token>
*/
generateSignedUrl: function (email) {
var now = Math.floor(Date.now() / 1000);
var exp = now + Math.min(this.session_len, 2592000); // enforce 30d max
// Subject (user identity)
var subject_email = (email && ('' + email).trim()) ||
gs.getProperty('x_sigma.email', this.client_id);
// Access claims
var account_type = (gs.getProperty('x_sigma.account_type', 'View') || 'View').trim();
// Teams (CSV → array)
var teams = [];
var teams_raw = (gs.getProperty('x_sigma.teams', '') || '').trim();
if (teams_raw) {
var parts = teams_raw.split(',');
for (var i = 0; i < parts.length; i++) {
var t = ('' + parts[i]).trim();
if (t) teams.push(t);
}
}
// user_attributes: gather all x_sigma.ua_* properties
var user_attributes = {};
var gr = new GlideRecord('sys_properties');
gr.addQuery('name', 'STARTSWITH', 'x_sigma.ua_');
gr.query();
while (gr.next()) {
var key = ('' + gr.getValue('name')).replace(/^x_sigma\.ua_/, '');
var val = '' + gr.getValue('value');
if (key) user_attributes[key] = val;
}
// Optional eval_connection_id
var eval_connection_id = (gs.getProperty('x_sigma.eval_connection_id', '') || '').trim();
// --- JWT header & payload (HS256) ---
var header = { alg: 'HS256', typ: 'JWT', kid: this.client_id };
var payload = {
sub: subject_email,
iss: this.client_id,
jti: gs.generateGUID(),
iat: now,
exp: exp,
account_type: account_type,
user_attributes: user_attributes
};
if (teams.length > 0) payload.teams = teams;
if (eval_connection_id) payload.eval_connection_id = eval_connection_id;
// Sign the token (pure-JS HMAC-SHA256 → compact JWS)
var token = this._sign_jwt(header, payload, this.secret);
// Base URL + required embed params
var joiner = (this.base_url.indexOf('?') >= 0) ? '&' : '?';
var url = this.base_url + joiner + ':embed=true&:jwt=' + encodeURIComponent(token);
// Optional UI params passthrough (if set as x_sigma.*)
var opt_keys = [
'disable_mobile_view', 'hide_menu', 'hide_folder_navigation', 'hide_tooltip',
'lng', 'menu_position', 'responsive_height', 'theme'
];
var extras = [];
for (var k = 0; k < opt_keys.length; k++) {
var prop = 'x_sigma.' + opt_keys[k];
var val = gs.getProperty(prop, null);
if (val !== null && val !== '' && typeof val !== 'undefined') {
extras.push(':' + opt_keys[k] + '=' + encodeURIComponent('' + val));
}
}
if (extras.length) url += '&' + extras.join('&');
if (this.debug) gs.info('[Sigma_Embed_API] final_url=' + url);
return url;
},
// ------------------------------------------------------------
// JWT signer (compact JWS) using pure-JS HMAC-SHA256
// ------------------------------------------------------------
_sign_jwt: function (header_obj, payload_obj, secret) {
var header_b64 = this._b64url_string(this._json(header_obj));
var payload_b64 = this._b64url_string(this._json(payload_obj));
var signing_in = header_b64 + '.' + payload_b64;
// Support base64-encoded secrets if x_sigma.secret_is_base64=true
var isB64 = (gs.getProperty('x_sigma.secret_is_base64', 'false') === 'true');
var keyBytes = isB64 ? this._from_b64(('' + secret).replace(/[\r\n]/g, '').trim())
: this._utf8(('' + secret));
var msgBytes = this._utf8(signing_in);
var sigBytes = this._hmac_sha256(msgBytes, keyBytes); // bytes
var sigB64 = this._b64_from_bytes(sigBytes); // Base64
var sigUrl = this._to_b64url(sigB64); // base64url
return signing_in + '.' + sigUrl;
},
// ------------------------------------------------------------
// Helpers: JSON, UTF-8, Base64, SHA-256, HMAC-SHA256
// ------------------------------------------------------------
_json: function (obj) { return new global.JSON().encode(obj); },
_utf8: function (str) {
var out = [], i = 0, c, c2;
for (; i < str.length; i++) {
c = str.charCodeAt(i);
if (c < 0x80) out.push(c);
else if (c < 0x800) out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
else if (c >= 0xd800 && c < 0xe000) {
i++; c2 = str.charCodeAt(i);
var u = 0x10000 + (((c & 0x3ff) << 10) | (c2 & 0x3ff));
out.push(0xf0 | (u >> 18), 0x80 | ((u >> 12) & 0x3f), 0x80 | ((u >> 6) & 0x3f), 0x80 | (u & 0x3f));
} else {
out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
}
}
return out;
},
_b64_from_bytes: function (bytes) {
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', out = '', i = 0;
for (; i < bytes.length; i += 3) {
var b0 = bytes[i] & 0xff,
b1 = (i + 1 < bytes.length) ? (bytes[i + 1] & 0xff) : 0,
b2 = (i + 2 < bytes.length) ? (bytes[i + 2] & 0xff) : 0;
var tri = (b0 << 16) | (b1 << 8) | b2;
out += chars[(tri >>> 18) & 0x3f];
out += chars[(tri >>> 12) & 0x3f];
out += (i + 1 < bytes.length) ? chars[(tri >>> 6) & 0x3f] : '=';
out += (i + 2 < bytes.length) ? chars[tri & 0x3f] : '=';
}
return out;
},
_to_b64url: function (b64) {
return ('' + b64).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
},
_b64url_string: function (plain_str) {
return this._to_b64url(this._b64_from_bytes(this._utf8(plain_str)));
},
// Base64 decoder (used only when x_sigma.secret_is_base64=true)
_from_b64: function (b64) {
var s = ('' + b64).replace(/[\r\n\s]/g, '');
var table = {}, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
for (var i = 0; i < chars.length; i++) table[chars.charAt(i)] = i;
var out = [], j = 0, acc = 0, bits = 0, c, val;
for (i = 0; i < s.length; i++) {
c = s.charAt(i); if (c === '=') break;
val = table[c]; if (val === undefined) continue;
acc = (acc << 6) | val; bits += 6;
if (bits >= 8) { bits -= 8; out[j++] = (acc >> bits) & 0xff; }
}
return out;
},
// SHA-256 (bytes → 32-byte array)
_sha256: function (bytes) {
function rotr(n, x){ return (x >>> n) | (x << (32 - n)); }
function Ch(x,y,z){ return (x & y) ^ (~x & z); }
function Maj(x,y,z){ return (x & y) ^ (x & z) ^ (y & z); }
function Σ0(x){ return rotr(2,x) ^ rotr(13,x) ^ rotr(22,x); }
function Σ1(x){ return rotr(6,x) ^ rotr(11,x) ^ rotr(25,x); }
function σ0(x){ return rotr(7,x) ^ rotr(18,x) ^ (x >>> 3); }
function σ1(x){ return rotr(17,x) ^ rotr(19,x) ^ (x >>> 10); }
var K = [
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
];
var l = bytes.length, bitHi = Math.floor(l / 0x20000000), bitLo = (l << 3) >>> 0;
var withOne = bytes.slice(0); withOne.push(0x80);
while ((withOne.length % 64) !== 56) withOne.push(0);
var words = [];
for (var i = 0; i < withOne.length; i += 4) {
words.push(((withOne[i] << 24) | (withOne[i+1] << 16) | (withOne[i+2] << 8) | (withOne[i+3] | 0)) >>> 0);
}
words.push(bitHi >>> 0); words.push(bitLo >>> 0);
var H0=0x6a09e667,H1=0xbb67ae85,H2=0x3c6ef372,H3=0xa54ff53a,
H4=0x510e527f,H5=0x9b05688c,H6=0x1f83d9ab,H7=0x5be0cd19;
var W = new Array(64);
for (var j = 0; j < words.length; j += 16) {
for (var t = 0; t < 16; t++) W[t] = words[j+t] >>> 0;
for (t = 16; t < 64; t++) W[t] = (σ1(W[t-2]) + W[t-7] + σ0(W[t-15]) + W[t-16]) >>> 0;
var a=H0,b=H1,c=H2,d=H3,e=H4,f=H5,g=H6,h=H7;
for (t = 0; t < 64; t++) {
var T1 = (h + Σ1(e) + Ch(e,f,g) + K[t] + W[t]) >>> 0;
var T2 = (Σ0(a) + Maj(a,b,c)) >>> 0;
h=g; g=f; f=e; e=(d + T1) >>> 0;
d=c; c=b; b=a; a=(T1 + T2) >>> 0;
}
H0=(H0+a)>>>0; H1=(H1+b)>>>0; H2=(H2+c)>>>0; H3=(H3+d)>>>0;
H4=(H4+e)>>>0; H5=(H5+f)>>>0; H6=(H6+g)>>>0; H7=(H7+h)>>>0;
}
var out = [];
function push(w){ out.push((w>>>24)&255,(w>>>16)&255,(w>>>8)&255,w&255); }
push(H0); push(H1); push(H2); push(H3); push(H4); push(H5); push(H6); push(H7);
return out;
},
// HMAC-SHA256 (bytes → 32-byte array)
_hmac_sha256: function (msgBytes, keyBytes) {
var block = 64; // 512-bit block size
if (keyBytes.length > block) keyBytes = this._sha256(keyBytes);
if (keyBytes.length < block) {
var pad = new Array(block - keyBytes.length);
for (var i = 0; i < pad.length; i++) pad[i] = 0;
keyBytes = keyBytes.concat(pad);
}
var oKey = [], iKey = [];
for (var j = 0; j < block; j++) { oKey[j] = keyBytes[j] ^ 0x5c; iKey[j] = keyBytes[j] ^ 0x36; }
return this._sha256(oKey.concat(this._sha256(iKey.concat(msgBytes))));
},
// Class marker
type: 'Sigma_Embed_API'
};
Click Submit
We need to create a REST API Resource that will act as the endpoint for generating and returning the Sigma embed JWT. This REST Resource will call the Sigma_Embed_API
Script Include we created earlier, receive the signed JWT, and return it to the widget or page requesting the embed.
Navigate to:
<your_instance>.service-now.com/sys_ws_definition_list.do
Click New
Sigma Embed
.sigma_embed
.Active
to checked.Click Submit
:
The UI will return to the Scripted REST APIs
page. Search for Sigma
and click into the Sigma Embed
just created.
Click the New
button in the lower right corner to create a new resource inside the service:
sigma-embed-url
.Active
to checked.GET
./sigma-embed-url
.Paste the following script in the * Script
box:
(function process(request, response) {
var email = (request.queryParams.email || gs.getProperty('x_sigma.email') || '').trim();
var svc = new Sigma_Embed_API();
var signedUrl = svc.generateSignedUrl(email);
// simplest: return an object and let SN serialize it
response.setStatus(200);
return { url: '' + signedUrl };
})(request, response);
Click Submit
:
We need to create a Service Portal Widget that will request the Sigma embed JWT from our REST Resource and then dynamically render the Sigma content inside an iframe
.
This widget will be placed on a page in the Service Portal.
Navigate to:
<your_instance>.service-now.com/sp_widget_list.do
Click New
Set the Name to Sigma iFrame
.
In each of the following sections we apply code to operations and styling to the Iframe.
In Body HTML template
copy/paste the following code:
<div>
<iframe ng-src="{{c.sigmaUrl}}" style="width:100%; height:90vh; border:0;" allowfullscreen></iframe>
</div>
In the CSS
section:
#sigma_embed {
width: 100%;
height: 90vh;
border: none;
}
In the Client controller
section:
api.controller = function($scope, $http, $sce) {
var c = this;
c.sigmaUrl = '';
c.$onInit = function() {
$http.get('/api/1807505/sigma_embed/sigma-embed-url').then(function(res) {
var rawUrl = (res.data && res.data.result && res.data.result.url) ||
(res.data && res.data.url) || '';
if (rawUrl) {
// Tell Angular this URL is safe to use in an <iframe>
c.sigmaUrl = $sce.trustAsResourceUrl(rawUrl);
} else {
console.error('[Sigma Iframe] Unexpected response payload:', res.data);
}
}, function(err) {
console.error('[Sigma Iframe] Failed to fetch signed URL', err);
});
};
};
In controllerAS
set the first box to c
and the Link
box to:
function link(scope, element, attrs, controller) {
}
Click Submit
:
We need to create a Service Portal Page that will host our Sigma iFrame widget. This page will be accessible through the Service Portal and is where the embedded Sigma content will actually appear.
Navigate to:
<your_instance>.service-now.com/$sp.do
Click the link for Add a new Page
:
Set the Page title
to Sigma QuickStart
and click Submit
.
Next, place a Container
on the page to hold the Iframe:
Drag the 12
column into the page container:
Using the Filter Widget
search box, locate the Sigma Iframe
and drag/drop it onto the page:
If everything is configured correctly, the Plugs Sales Transactions
table should appear:
Click on the icon in the upper-right corner to jump to the webpage:
We have successfully embedded Sigma into ServiceNow:
In this QuickStart, we:
You now have a working example of embedding Sigma into ServiceNow using a secure JWT-based approach. While this QuickStart demonstrates the core process, adapting it for production should include additional security hardening, adherence to ServiceNow development best practices, and proper user access controls.
Additional Resource Links
Blog
Community
Help Center
QuickStarts
Be sure to check out all the latest developments at Sigma's First Friday Feature page!