// Six Years On The Platform · Production Experience
ServiceNow Architecture
The undocumented corners of the platform. Production-proven patterns in API design, integration architecture, authentication engineering, and upgrade governance. Written from real delivery, not demos.
// 01
Specialisms
001
API Architecture & Dynamic Generation
Scripted REST frameworks where configuration records define endpoints at runtime. OpenAPI-first delivery. Non-developer API onboarding. Tenant-aware response shaping.
002
Composite Authentication Design
Layering Bearer tokens, mTLS, and HMAC-signed headers on single outbound calls. Custom OAuth exchanges, JWT validation, Connection and Credential Alias architecture for high-security consumers.
003
Transactional API Engineering
Multi-step APIs with staging, transformation, orchestration, and rollback. Async callbacks and downstream failure handling across CRM, middleware, and financial system integrations.
004
ACL-Driven Payload Filtering
Using the ACL engine to dynamically strip response fields by calling principal. Multi-tenant data segregation without forking endpoint logic or adding middleware.
005
Scoped Application Development
End-to-end scoped app architecture: data model, business logic, Service Portal widgets in AngularJS, Flow Designer orchestration, cross-scope API exposure. ISO 27001 and GDPR aligned.
006
Upgrade Governance
Structured pre-upgrade regression protocols, post-upgrade certification, and platform communication. Authored a process at Fieldfisher that outlasted my departure — that is the measure.
// 02
Code Playground
Safe Reference Resolution in Async Business Rules
The getRefRecord() async context trap — and the fix
// In async context, getRefRecord() may return stale data.// The GlideElement snapshot captured at queue time carries cached// display values that diverge from the committed database state.// Wrong — unreliable in any async Business Rulevar group = current.assignment_group.getRefRecord();
var manager = group.manager.toString(); // may be stale// Correct — extract sys_id, drive a fresh queryvar groupSysId = current.assignment_group.toString();
if (groupSysId) {
var group = newGlideRecord('sys_user_group');
if (group.get(groupSysId)) {
// Fresh database read. Always correct.var managerSysId = group.manager.toString();
var groupName = group.name.toString();
}
}
The rule: in async context, treat current as a bag of sys_ids and primitive values only. toString() on a reference field reliably returns the sys_id from the snapshot — only the cached display metadata is stale, not the value. Use that sys_id to drive an explicit fresh GlideRecord.get(). Full article with diagnostics and examples here.
ACL-Driven Payload Filtering by Calling Principal
Strip response fields dynamically — no endpoint forks
// In your Scripted REST Resource handler
(function process(request, response) {
var record = newGlideRecord('incident');
if (!record.get(request.pathParams.sys_id)) {
response.setStatus(404); return;
}
var payload = {
sys_id: record.sys_id.toString(),
number: record.number.toString(),
short_desc: record.short_description.toString(),
state: record.state.toString(),
// Sensitive — conditionally included
caller_id: record.caller_id.toString(),
work_notes: record.work_notes.toString(),
cost_centre: record.u_cost_centre.toString()
};
// Resolve caller identity from OAuth token or headervar callerSysId = request.getHeader('X-Caller-SysId');
if (!callerHasRole(callerSysId, 'u_api_sensitive')) {
delete payload.caller_id;
delete payload.work_notes;
delete payload.cost_centre;
}
response.setContentType('application/json');
response.setBody(JSON.stringify(payload));
})(request, response);
One endpoint, multiple consumers with different field visibility. The consumer's identity is resolved from the request header or OAuth token, their roles are checked against sys_user_has_role, and sensitive fields are deleted from the payload object before serialisation. More robust than separate endpoint versions — access control is enforced at the API boundary, not delegated to the consumer.
All three auth mechanisms on a single outbound REST call
functioncallSecureEndpoint(endpoint, payload) {
var r = new sn_ws.RESTMessageV2();
r.setEndpoint(endpoint);
r.setHttpMethod('POST');
r.setRequestBody(JSON.stringify(payload));
r.setHttpHeader('Content-Type', 'application/json');
// Layer 1: Bearer tokenvar token = getBearerToken();
r.setHttpHeader('Authorization', 'Bearer ' + token);
// Layer 2: HMAC signed header// Set body BEFORE computing signature — it covers the payloadvar secret = gs.getProperty('u_api_hmac_secret');
var timestamp = newGlideDateTime().getNumericValue().toString();
var digest = newGlideDigest();
var sig = digest.getSHA256HMACBase64(
secret, JSON.stringify(payload) + timestamp
);
r.setHttpHeader('X-Timestamp', timestamp);
r.setHttpHeader('X-Signature', sig);
// Layer 3: mTLS via Connection and Credential Alias
r.setMutualAuth('u_mtls_client_alias');
var resp = r.execute();
if (resp.getStatusCode() !== 200) {
gs.logError('Secure call failed: ' + resp.getStatusCode());
return null;
}
return JSON.parse(resp.getBody());
}
The critical ordering: set the request body before computing the HMAC — the signature must cover the payload. setMutualAuth() references a Connection and Credential Alias configured with the client certificate; the MID Server handles the TLS handshake. This combination is required by some high-security financial and government integrations that will not accept any single auth mechanism alone.
Safe Dot-Walk Replacement for Async Context
Breaking reference chains into explicit queries
// Dot-walking works fine in sync Business Rules.// In async context, each reference hop may resolve stale data.// Extract into a Script Include — keep the BR readable.// Goal: email of the manager of the incident assignment group// Wrong in async: current.assignment_group.manager.email.toString()functiongetGroupManagerEmail(groupSysId) {
if (!groupSysId) return null;
var group = newGlideRecord('sys_user_group');
if (!group.get(groupSysId)) return null;
var managerSysId = group.manager.toString();
if (!managerSysId) return null;
var manager = newGlideRecord('sys_user');
if (!manager.get(managerSysId)) return null;
return manager.email.toString() || null;
}
// In the Business Rule:var email = getGroupManagerEmail(
current.assignment_group.toString()
);
Verbose, but debuggable at every step and null-safe at every reference boundary. This pattern is safe in both sync and async context, which is the point — write it this way once and you never have to revisit it when the execution context changes.
Runtime Endpoint Registration from Config Records
Scripted REST APIs defined by data, not code deployments
// API Registry table: u_api_registry// Fields: u_active, u_path, u_handler, u_auth_type, u_acl_role// Called from Script Include on scoped app activationfunctionregisterAPIEndpoints() {
var registry = newGlideRecord('u_api_registry');
registry.addQuery('u_active', true);
registry.query();
while (registry.next()) {
createResource(
registry.u_path.toString(),
registry.u_handler.toString(),
registry.u_auth_type.toString(),
registry.u_acl_role.toString()
);
}
}
functioncreateResource(path, handler, authType, aclRole) {
var exists = newGlideRecord('sys_ws_endpoint');
exists.addQuery('relative_path', path);
exists.query();
if (exists.next()) return; // already registeredvar ep = newGlideRecord('sys_ws_endpoint');
ep.newRecord();
ep.setValue('relative_path', path);
ep.setValue('script_include', handler);
ep.setValue('requires_authentication', authType !== 'none');
ep.setValue('required_role', aclRole);
ep.insert();
}
This pattern removes developers from routine API onboarding. A product owner creates a row in the registry table — path, handler Script Include, auth type, required role — and the endpoint is live without a code deployment or a change request. The registry table also serves as self-documenting API inventory, which solves a separate problem most instances have.
Auto-Generating REST APIs from Configuration Records
How to build a framework inside a scoped app that reads from a registry table and creates Scripted REST Resources at runtime. Non-developers onboard new consumers without raising a change request or touching code.
Coming soon
🔒
COMING SOON
Auth & Security
Composite Auth: Bearer + mTLS + Signed Headers in One Request
The production patterns for securing outbound REST calls when the receiving system demands more than one auth mechanism simultaneously. What the docs do not tell you about layering these in RESTMessageV2.
Coming soon
▲
COMING SOON
Upgrade Governance
The Upgrade That Silently Changed My Business Rules
A Washington-to-Xanadu post-mortem. Three Business Rules stopped working correctly with no error, no skip log, no warning. How to build a regression harness that catches silent context promotion before it hits production.
Coming soon
// 04
Platform Background
Senior ServiceNow Developer
Oct 2023 – Present
Capgemini · ServiceNow Practice
►Complex ITSM, CSM, and bespoke scoped application delivery across public and private sector clients. ISO 27001 and GDPR compliant integration work throughout.
►Pre-sales solutioning and technical demonstrations for strategic pursuits. Peer review and coaching for junior developers on the practice.
ServiceNow Developer & Administrator
Oct 2019 – Oct 2022
Fieldfisher · International Law Firm
►Sole technical owner of the ServiceNow platform for 800+ staff. Full end-to-end responsibility — security architecture, ACL design, upgrades, cloning, integrations, and custom development — without a team or internal precedent.
►Authored the upgrade regression protocol covering multiple major ServiceNow releases. Still in active use after departure, which is the only measure that matters.