Skip to main content

Axur Feeds - ServiceNow

How to replace the native integration with a Feed (Push or Pull) and keep creating ServiceNow incidents in the same format as before.

Overview

The native integration sent detection.opened events straight to a ServiceNow table. With Feeds, you choose which tickets are delivered, and a small script in ServiceNow turns each ticket into an Incident record.

Figure 1: The two delivery modes. Both use the same JSON and the same mapping script.

Figure 1: The two delivery modes. Both use the same JSON and the same mapping script.

  • Push (Webhook 2.0): near real-time

    • The Axur Platform sends tickets to an endpoint you create in ServiceNow. Closest to the native integration, but your instance must accept inbound calls.

  • Pull (polling): easier to approve

    • ServiceNow queries the feed every X minutes. Traffic is outbound only, and latency equals the schedule interval.

Prerequisites

A ServiceNow admin user (or web_service_admin for Push), a manager user on the Axur Platform to create the feed, and an Axur Platform API Key (for Pull).


Create the feed in the Axur Platform

  1. Go to one.axur.com/api-integrations → Feeds section → Add feed → Skip templates.

  2. Choose the Tickets type.

  3. Choose the delivery mode: Pull or Push. For Push, enter the endpoint URL you create in step 6.

  4. Under Event date, select First detection (ticket.creation.date). Each ticket arrives only once, just like the native integration's detection.opened.

  5. Under Feed parameters, leave it blank to receive everything, or filter, e.g. type=phishing,fake-social-media-profile.

  6. Set a title, e.g. “ServiceNow – Incidents”, and save. Write down the feed ID/URL.

Figure 2: Suggested feed configuration (illustration; the actual screen may look different).

Also want re-detections?

Use Any detection (open.date). The script below updates the existing incident by ticketKey (correlation_id), so no incident is ever duplicated.


Field mapping

Each item collectionData.tickets[] becomes a record in the Incident table. The colors show where each piece of information comes from:


Mapping table

ServiceNow field

Source in the JSON

Notes

state

fixed 1

Set on creation only. Updates do not reopen the incident.

correlation_id

ticket.ticketKey

Used to avoid duplicates.

correlation_display

ticket.ticketKey

Same as the native integration.

short_description

Workspace - Type - customerKey - reference (ticketKey)

Same format as the native integration. Capped at 160 characters: only the URL is shortened (with ...).

description

Native template (Figure 3b)

Link: https://one.axur.com/brand-protection/tickets/{ticketKey}

Watch out for dotted keys

Fields like open.date and creation.date have a dot in their name. In JavaScript, use brackets: d['open.date'], not d.open.date.


Script Include: the mapper

This script builds the fields and creates or updates the incident. You create it once, and both Push and Pull use it.

  1. In ServiceNow, go to System Definition › Script Includes → New.

  2. Name: AxurFeedMapper (must be exactly this name).

  3. Paste the code below into the Script field and save.

Figure 4: Creating the Script Include (illustration).

TYPE_LABELS and WORKSPACES hold the official labels used by the Axur Platform, so the text matches the native integration. Types not in the list fall back to Axur Surface the raw slug, so nothing breaks.

var AxurFeedMapper = {
TABLE: 'incident',
TICKET_URL: 'https://one.axur.com/brand-protection/tickets/',
SHORT_DESCRIPTION_LIMIT: 160,
DEFAULT_WORKSPACE: 'Axur Surface',

WORKSPACES: {
'digital-fraud': 'Digital Fraud',
'online-piracy': 'Online Piracy',
'data-leakage': 'Data Leakage',
'deep-dark-web': 'Deep & Dark Web',
'executives-vips': 'Executives & VIPs'
},

TYPE_LABELS: {
'phishing': { workspace: 'digital-fraud', name: 'Phishing' },
'paid-search': { workspace: 'digital-fraud', name: 'Brand use in paid search' },
'malware': { workspace: 'digital-fraud', name: 'Malware' },
'fake-social-media-profile': { workspace: 'digital-fraud', name: 'Fake social media profile' },
'similar-domain-name': { workspace: 'digital-fraud', name: 'Similar domain name' },
'fake-mobile-app': { workspace: 'digital-fraud', name: 'Fake mobile app' },
'fraudulent-brand-use': { workspace: 'digital-fraud', name: 'Fraudulent brand use' },
'unauthorized-sale': { workspace: 'online-piracy', name: 'Counterfeit or irregular sale' },
'unauthorized-distribution': { workspace: 'online-piracy', name: 'Content piracy' },
'corporate-credential-leak': { workspace: 'data-leakage', name: 'Corporate credential leak' },
'code-secret-leak': { workspace: 'data-leakage', name: 'Code secret leak' },
'database-exposure': { workspace: 'data-leakage', name: 'Database exposure' },
'document-leak': { workspace: 'data-leakage', name: 'Document leak' },
'other-sensitive-data': { workspace: 'data-leakage', name: 'Other sensitive data' },
'infostealer-credential': { workspace: 'data-leakage', name: 'Infostealer credentials' },
'dw-activity': { workspace: 'deep-dark-web', name: 'Deep & Dark Web activity' },
'data-exposure-website': { workspace: 'deep-dark-web', name: 'Data exposure in website' },
'data-sale-website': { workspace: 'deep-dark-web', name: 'Data sale in website' },
'fraud-tool-scheme-website': { workspace: 'deep-dark-web', name: 'Fraud tool or scheme in website' },
'suspicious-activity-website': { workspace: 'deep-dark-web', name: 'Suspicious activity in website' },
'data-exposure-message': { workspace: 'deep-dark-web', name: 'Data exposure in messaging app' },
'data-sale-message': { workspace: 'deep-dark-web', name: 'Data sale in messaging app' },
'fraud-tool-scheme-message': { workspace: 'deep-dark-web', name: 'Fraud tool or scheme in messaging app' },
'suspicious-activity-message': { workspace: 'deep-dark-web', name: 'Suspicious activity in messaging app' },
'infrastructure-exposure': { workspace: 'deep-dark-web', name: 'Infrastructure exposure' },
'ransomware-attack': { workspace: 'deep-dark-web', name: 'Ransomware attack alert' },
'executive-fake-social-media-profile': { workspace: 'executives-vips', name: 'Fake social media profile' },
'executive-personalinfo-leak': { workspace: 'executives-vips', name: 'Personal information leak' },
'executive-credential-leak': { workspace: 'executives-vips', name: 'Credential leak' },
'executive-mobile-phone': { workspace: 'executives-vips', name: 'Phone number leak' },
'executive-card-leak': { workspace: 'executives-vips', name: 'Credit card leak' }
},

buildFields: function (item) {
var t = item.ticket || {};
var d = item.detection || {};
var ticketKey = t.ticketKey;
var type = d.type || '';
var label = this.TYPE_LABELS[type] || { workspace: '', name: type };
var workspace = this.WORKSPACES[label.workspace] || this.DEFAULT_WORKSPACE;
var threatUrl = t.reference || '';

var prefix = workspace + ' - ' + label.name + ' - ' + t.customerKey + ' - ';
var suffix = ' (' + ticketKey + ')';
var shortDescription = prefix + this.fitReference(threatUrl, prefix.length + suffix.length) + suffix;

var description = [
'Axur - one.axur.com',
'',
'New threat detected (' + this.TICKET_URL + ticketKey + ')',
'',
'Ticket Key: ' + ticketKey,
this.assetsLine(d.assets || []),
'Ticket Type: ' + label.name + ' (' + type + ')',
'Threat URL: ' + threatUrl,
'Detection Date: ' + this.formatDate(d['open.date'] || t['creation.date'])
].join('\n');

return { ticketKey: ticketKey, shortDescription: shortDescription, description: description };
},

fitReference: function (reference, usedLength) {
var available = this.SHORT_DESCRIPTION_LIMIT - usedLength;
if (reference.length <= available) return reference;
return reference.substring(0, Math.max(available - 3, 0)) + '...';
},

assetsLine: function (assets) {
return assets.length === 1 ? 'Asset: ' + assets[0] : 'Assets: (' + assets.join(', ') + ')';
},

formatDate: function (iso) {
if (!iso) return '';
return iso.substring(8, 10) + '/' + iso.substring(5, 7) + '/' + iso.substring(0, 4);
},

upsert: function (item) {
var f = this.buildFields(item);
if (!f.ticketKey) return;

var gr = new GlideRecord(this.TABLE);
gr.addQuery('correlation_id', f.ticketKey);
gr.query();
var exists = gr.next();
if (!exists) {
gr.initialize();
gr.state = 1;
}
gr.correlation_id = f.ticketKey;
gr.correlation_display = f.ticketKey;
gr.short_description = f.shortDescription;
gr.description = f.description;
if (exists) { gr.update(); } else { gr.insert(); }
},

processPayload: function (payload) {
var tickets = (payload && payload.collectionData && payload.collectionData.tickets) || [];
for (var i = 0; i < tickets.length; i++) {
this.upsert(tickets[i]);
}
return tickets.length;
}
};

Different table?

If records should not go to incident, change the TABLE value. The table needs the correlation_id, short_description and description fields.


Option A: Pull with a Scheduled Job

Pick this option if your instance cannot receive external calls.

Store the URL and API Key

  1. Go to sys_properties.list (type it in the navigation filter) → New.

  2. Create axur.feed.url with the feed URL, e.g. https://api.axur.com/gateway/1.0/api/integration-feed/feeds/feed/<ID.>

  3. Create axur.feed.api_key with the Axur Platform API Key and restrict Read roles to admin.

Security

Never write the API Key directly in the script. If your security team prefers, use Connection & Credential Aliases instead of the system property.

Create the schedule

  1. Go to System Definition › Scheduled Jobs → New → Automatically run a script of your choosing.

  2. Name: Axur Feed → Incident. Run: Periodically. Repeat interval: 00:05:00 (5 min).

  3. Paste the script below and save.

Figure 5: Scheduled Job running every 5 minutes (illustration).

var FEED_URL = gs.getProperty('axur.feed.url');
var API_KEY = gs.getProperty('axur.feed.api_key');
var MAX_PAGES = 20;

for (var page = 0; page < MAX_PAGES; page++) {
var request = new sn_ws.RESTMessageV2();
request.setEndpoint(FEED_URL);
request.setHttpMethod('GET');
request.setRequestHeader('Authorization', 'Bearer ' + API_KEY);
request.setRequestHeader('Accept', 'application/json');

var response = request.execute();
if (response.getStatusCode() != 200) {
gs.error('Axur feed: HTTP ' + response.getStatusCode() + ' - ' + response.getBody());
break;
}

var processed = AxurFeedMapper.processPayload(JSON.parse(response.getBody()));
if (processed === 0) break;
}

Each call to the feed moves the pointer forward and returns the next page. The loop runs until a page comes back empty, capped at 20 pages per run so the job never hangs. See the API documentation for the authentication header format.


Option B: Push with a Scripted REST API

Pick this option to receive tickets in near real-time. Your instance must accept inbound calls from the Axur Platform.

  1. Go to System Web Services › Scripted Web Services › Scripted REST APIs → New.

  2. Name: Axur Feed, API ID: axur_feed. Save.

  3. On the Resources tab → New: Name tickets, HTTP method POST, Relative path /tickets.

  4. Paste the script below into the resource and save.

  5. Copy the Resource path (e.g. https://<instance>.service-now.com/api/<scope>/axur_feed/tickets) and enter it as the Push feed endpoint in the Axur Platform.

Figure 6: POST resource that receives Webhook 2.0 (illustration).

(function process(request, response) {
var processed = AxurFeedMapper.processPayload(request.body.data);
response.setStatus(200);
response.setBody({ processed: processed });
})(request, response);

Endpoint authentication

By default, the resource requires ServiceNow authentication (Requires authentication). Set it up according to the authentication options available in the Axur Platform Push feed (e.g. a dedicated integration user that can only write to the Incident table). Do not disable authentication.


Test and validate

  1. Test without moving the pointer: for Pull, append ?dry-run=true to axur.feed.url. You get the same tickets every time and nothing is lost.

  2. Run the job manually: open the Scheduled Job → Execute Now.

  3. Check the incidents under Incident › All, filtering by a non-empty Correlation ID.

  4. Run it again. Thanks to the upsert, the number of incidents should not grow.

  5. Check for errors under System Logs › Errors (messages starting with Axur feed:).

  6. All good? Remove ?dry-run=true so the pointer starts moving forward.

Tip

Validate on a sub-production or development instance before enabling it in production.


Limitations and checklist

Native integration

With Feeds

detection.opened creates the record

✅ Covered (event date First detection or Any detection)

short_description, description, correlation_display, state fields

✅ Same format, built by the script

New detections only

✅ And more: filters by type, incident, takedown, any update…


Go-live checklist

  • Feed created in the Axur Platform with the right event date and filters

  • AxurFeedMapper Script Include created

  • TYPE_LABELS table updated with the types you use

  • Pull: axur.feed.url and axur.feed.api_key properties created (read-restricted)

  • Pull: Scheduled Job active · Push: endpoint registered on the feed

  • Tested with ?dry-run=true, no duplicates

  • ?dry-run=true removed


Did this answer your question?