logo

NJP

Automating Historical Data Synchronization: Sending Old vs. New Attribute Payloads to Okta

New article articles in ServiceNow Community · Dec 31, 2025 · article

Requirement Overview

The core objective is to automate the extraction of modified user attributes from the Lifecycle Event (LE) Case and its associated records. When a transfer occurs, the system must:

  • Identify the specific fields changed (e.g., Department, Job Code, Business Unit).
  • Compare the previous values (Old) with the current values (New).
  • Construct a secure JSON payload and transmit it to Okta to ensure downstream access is updated accurately

Technical Implementation

1. Data Extraction Logic

Script Include: IC_Transfer_FieldHistory_OKTA_Payload

It takes the HR case sys_id, identifies the subject person, and retrieves old and new field values from ServiceNow field history records (sys_audit / history data) using IC_Transfer_GetFieldValues_FromHistory.

User-related changes (department, job code, employee type, title) and HR profile changes (region, company, country, workday location) are fetched from their respective tables.

All old and new values are consolidated into a structured JSON response for integration use.

Client Callable script include Code and scope is Human Resources: Lifecycle Events

var IC_Transfer_FieldHistory_OKTA_Payload_= Class.create(); IC_Transfer_FieldHistory_OKTA_Payload.prototype = Object.extendsObject(global.AbstractAjaxProcessor, { // Function to fetch all field changes (old and new values) for the subject person (sys_id) getAllFieldChanges: function(caseSysId) { // Initialize the result object var result = { "departmentNames": [], "departmentIds": [], // Array to store department IDs "businessUnitNames": [], "businessUnitIds": [], "jobCodeChanges": [], "empTypeChanges": [], "titleChanges": [], "hrProfileChanges": { "regionChanges": [], "companyChanges": [], "countryCodeChanges": [], "workdayLocationChanges": [] } }; if (!caseSysId) { gs.error('Sys_id is required to fetch field history'); return 'Sys_id parameter is missing'; } // Query to fetch the HR case by sys_id var leCase = new GlideRecord("sn_hr_le_case"); if (leCase.get(caseSysId)) { var subPerson = leCase.subject_person; // Fetch and process department-related data var depChanges = new global.IC_Transfer_GetFieldValues_FromHistory().getFieldChanges('sys_user', subPerson, 'department'); var departmentData = this.getDepartmentAndBusinessUnitData(depChanges); result.departmentNames = departmentData.names; result.departmentIds = departmentData.ids; // Add department IDs to result result.businessUnitNames = departmentData.businessUnitNames; result.businessUnitIds = departmentData.businessUnitIds; // Fetch and store other field changes result.jobCodeChanges = new global.IC_Transfer_GetFieldValues_FromHistory().getFieldChanges('sys_user', subPerson, 'u_job_code'); result.empTypeChanges = new global.IC_Transfer_GetFieldValues_FromHistory().getFieldChanges('sys_user', subPerson, 'x_blglp_workday_in_employee_type'); result.titleChanges = new global.IC_Transfer_GetFieldValues_FromHistory().getFieldChanges('sys_user', subPerson, 'title'); // Fetch HR profile related fields var hrProfile = new GlideRecord("sn_hr_core_profile"); hrProfile.addQuery('user', subPerson); hrProfile.query(); if (hrProfile.next()) { // Store HR profile changes result.hrProfileChanges.regionChanges = new global.IC_Transfer_GetFieldValues_FromHistory().getFieldChanges('sn_hr_core_profile', hrProfile.sys_id, 'u_region'); result.hrProfileChanges.companyChanges = new global.IC_Transfer_GetFieldValues_FromHistory().getFieldChanges('sn_hr_core_profile', hrProfile.sys_id, 'u_company_name'); result.hrProfileChanges.countryCodeChanges = new global.IC_Transfer_GetFieldValues_FromHistory().getFieldChanges('sn_hr_core_profile', hrProfile.sys_id, 'u_country'); result.hrProfileChanges.workdayLocationChanges = new global.IC_Transfer_GetFieldValues_FromHistory().getFieldChanges('sn_hr_core_profile', hrProfile.sys_id, 'u_workday_location'); } } else { gs.error("No HR case found for sys_id: " + caseSysId); } // Return the result as a JSON object return result; }, type: 'IC_FieldHistory_OKTA_Payload' });

2. Script Include: IC_Transfer_GetFieldValues_FromHistory

This script is responsible for generating the history set and extracting the specific field transitions (old value and new value) from the sys_history_line table.

var IC_Transfer_GetFieldValues_FromHistory = Class.create(); IC_Transfer_GetFieldValues_FromHistory.prototype = { initialize: function() {}, getFieldChanges: function(tableName, documentKey, fieldName) { var changes = []; var hw, createSet = new GlideRecord(tableName); //to create the history set createSet.get(documentKey); try { var historySetRec = new GlideHistorySet(tableName, documentKey); historySetRec.generate(); GlideHistorySet(tableName, documentKey).refresh(); } catch (e) { gs.info("COMPARISONTIME ERROR: " + e); } var historySet = new GlideRecord("sys_history_set"); historySet.addQuery("table", tableName); historySet.addQuery("id", documentKey); historySet.query(); if (historySet.next()) { var historyLine = new GlideRecord("sys_history_line"); historyLine.addQuery('set', historySet.sys_id); historyLine.addQuery("field", fieldName); historyLine.addQuery("update_time", ">=", gs.minutesAgo(1440)); // 1440 minutes = 24 hours historyLine.orderByDesc("update_time"); historyLine.query(); if (historyLine.next()) { var oldValue = historyLine.getValue('old') || historyLine.getValue('new'); var newValue = historyLine.getValue('new'); changes.push(oldValue, newValue); } else { // If no history found, set both old and new to the current value var currentRecord = new GlideRecord(tableName); if (currentRecord.get(documentKey)) { var currentValue = currentRecord.getValue(fieldName); changes.push(currentValue, currentValue); } } } return changes; }, type: 'IC_Transfer_GetFieldValues_FromHistory' };

3. Payload Construction
Once the history is captured, a Flow Action compiles the data into the final JSON format required by Okta.

Code of flow action Payload

IC-Generate Payload: Generates a complete payload with both old and new user attributes using IC_Transfer_FieldHistory_OKTA_Payload script include

(function execute(inputs, outputs) { var grHRCase = new GlideRecord(String(inputs.hr_case_table)) grHRCase.get(String(inputs.hr_case_sys_id)); grHRCase.query(); if(grHRCase.next()) { var caseSysId = String(inputs.hr_case_sys_id); var getFields = new sn_hr_le.IC_Transfer_FieldHistory_OKTA_Payload(); var getFieldValues = getFields.getAllFieldChanges(caseSysId) outputs.new_department = getFieldValues.departmentNames[1]; outputs.old_department = getFieldValues.departmentNames[0]; outputs.new_business_unit = getFieldValues.businessUnitNames[1]; outputs.old_business_unit = getFieldValues.businessUnitNames[0]; outputs.old_region = getFieldValues.hrProfileChanges.regionChanges[0]; outputs.new_region = getFieldValues.hrProfileChanges.regionChanges[1]; outputs.old_job_code = getFieldValues.jobCodeChanges[0]; outputs.new_job_code = getFieldValues.jobCodeChanges[1]; var transferEffectiveDateTime = new GlideDateTime(grHRCase.u_effective_date); var transferEffectiveTime = String(transferEffectiveDateTime).replace(" ", "T") + "Z"; var payload = { "user_id": String(grHRCase.subject_person.employee_number), "event": { "id": String(grHRCase.number), "type": String(inputs.event_type), "status": String(inputs.status), "details": { "SNOW_RITM": String(inputs.provision_access_ritm), "effective_time": String(transferEffectiveTime), "old_user_attributes":{ "firstName": String(grHRCase.subject_person_hr_profile.legal_first_name), "lastName": String(grHRCase.subject_person_hr_profile.u_legal_last_name), "userType": String(getFieldValues.empTypeChanges[0].toUpperCase()), "wd_jobCode": String(getFieldValues.jobCodeChanges[0]), "title": String(getFieldValues.titleChanges[0]), "division": String(getFieldValues.businessUnitNames[0]), "wd_BusinessUnitID": String(getFieldValues.businessUnitIds[0]), "department": String(getFieldValues.departmentNames[0]), "wd_DepartmentID": String(getFieldValues.departmentIds[0]), "wd_region": String(getFieldValues.hrProfileChanges.regionChanges[0]), "countryCode": String(getFieldValues.hrProfileChanges.countryCodeChanges[0]), "wd_location": String(getFieldValues.hrProfileChanges.workdayLocationChanges[0]), "wd_company": String(getFieldValues.hrProfileChanges.companyChanges[0]) }, "new_user_attributes":{ "firstName": String(grHRCase.subject_person_hr_profile.legal_first_name), "lastName": String(grHRCase.subject_person_hr_profile.u_legal_last_name), "userType": String(getFieldValues.empTypeChanges[1].toUpperCase()), "wd_jobCode": String(getFieldValues.jobCodeChanges[1]), "title": String(getFieldValues.titleChanges[1]), "division": String(getFieldValues.businessUnitNames[1]), "wd_BusinessUnitID": String(getFieldValues.businessUnitIds[1]), "department": String(getFieldValues.departmentNames[1]), "wd_DepartmentID": String(getFieldValues.departmentIds[1]), "wd_region": String(getFieldValues.hrProfileChanges.regionChanges[1]), "countryCode": String(getFieldValues.hrProfileChanges.countryCodeChanges[1]), "wd_location": String(getFieldValues.hrProfileChanges.workdayLocationChanges[1]), "wd_company": String(getFieldValues.hrProfileChanges.companyChanges[1]) } } } } outputs.payload = JSON.stringify(payload); } })(inputs, outputs);

If my article helped you, please mark it as helpful.

Thank you!

View original source

https://www.servicenow.com/community/hrsd-articles/automating-historical-data-synchronization-sending-old-vs-new/ta-p/3458618