IntegrationHub for ServiceNow
IntegrationHub provides reusable integration components for Flow Designer and workflows.
IntegrationHub Architecture
Spoke (sn_ih_spoke) ├── Actions (sn_ih_action) │ ├── Inputs │ ├── Steps │ └── Outputs ├── Connection Alias └── Credential Alias
Flow Designer └── Uses Spoke Actions
Key Tables
Table Purpose
sn_ih_spoke
Spoke definitions
sn_ih_action
Spoke actions
sn_ih_step_config
Action step configuration
sys_alias
Connection/credential aliases
Connection & Credential Aliases (ES5)
Create Connection Alias
// Create connection alias (ES5 ONLY!) var alias = new GlideRecord("sys_alias") alias.initialize()
alias.setValue("name", "Jira Connection") alias.setValue("id", "x_myapp_jira_connection") alias.setValue("type", "connection") alias.setValue("connection_type", "http")
// Connection attributes alias.setValue( "attributes", JSON.stringify({ base_url: "https://mycompany.atlassian.net/rest/api/3", timeout: 30000, }), )
alias.insert()
Create Credential Alias
// Create credential alias (ES5 ONLY!) var credAlias = new GlideRecord("sys_alias") credAlias.initialize()
credAlias.setValue("name", "Jira API Token") credAlias.setValue("id", "x_myapp_jira_credential") credAlias.setValue("type", "credential")
credAlias.insert()
// Link to actual credential var credential = new GlideRecord("basic_auth_credentials") credential.initialize() credential.setValue("name", "Jira API Token Credential") credential.setValue("user_name", "api-user@company.com") credential.setValue("password", "") // Set via secure method credential.insert()
// Create alias-credential mapping var mapping = new GlideRecord("sys_alias_credential") mapping.initialize() mapping.setValue("alias", credAlias.getUniqueValue()) mapping.setValue("credential", credential.getUniqueValue()) mapping.insert()
Spoke Development (ES5)
Create Custom Spoke
// Create spoke (ES5 ONLY!) var spoke = new GlideRecord("sn_ih_spoke") spoke.initialize()
spoke.setValue("name", "Custom ITSM Connector") spoke.setValue("label", "Custom ITSM Connector") spoke.setValue("description", "Integration with external ITSM system") spoke.setValue("vendor", "My Company") spoke.setValue("version", "1.0.0")
// Scope spoke.setValue("scope", "x_myapp_itsm")
// Logo spoke.setValue("logo", "attachment_sys_id")
spoke.insert()
Create Spoke Action
// Create action for spoke (ES5 ONLY!) var action = new GlideRecord("sn_ih_action") action.initialize()
action.setValue("name", "Create External Ticket") action.setValue("label", "Create External Ticket") action.setValue("spoke", spokeSysId) action.setValue("description", "Creates a ticket in external ITSM system")
// Category action.setValue("category", "record_operations")
// Accessibility action.setValue("accessible_from", "flow_designer")
action.insert()
// Add inputs addActionInput(action.getUniqueValue(), "summary", "String", true, "Ticket summary") addActionInput(action.getUniqueValue(), "description", "String", false, "Ticket description") addActionInput(action.getUniqueValue(), "priority", "String", false, "Priority level")
// Add outputs addActionOutput(action.getUniqueValue(), "ticket_id", "String", "Created ticket ID") addActionOutput(action.getUniqueValue(), "ticket_url", "String", "URL to ticket")
Action Input/Output Helpers
// Add action input (ES5 ONLY!) function addActionInput(actionSysId, name, type, mandatory, label) { var input = new GlideRecord("sn_ih_input") input.initialize() input.setValue("action", actionSysId) input.setValue("name", name) input.setValue("label", label) input.setValue("type", type) input.setValue("mandatory", mandatory) input.setValue("order", getNextOrder(actionSysId, "input")) return input.insert() }
// Add action output (ES5 ONLY!) function addActionOutput(actionSysId, name, type, label) { var output = new GlideRecord("sn_ih_output") output.initialize() output.setValue("action", actionSysId) output.setValue("name", name) output.setValue("label", label) output.setValue("type", type) output.setValue("order", getNextOrder(actionSysId, "output")) return output.insert() }
Action Steps (ES5)
REST Step Configuration
// Create REST step for action (ES5 ONLY!) var step = new GlideRecord("sn_ih_step_config") step.initialize()
step.setValue("action", actionSysId) step.setValue("name", "Call External API") step.setValue("order", 100) step.setValue("step_type", "rest")
// REST configuration step.setValue( "rest_config", JSON.stringify({ connection_alias: "x_myapp_jira_connection", credential_alias: "x_myapp_jira_credential", http_method: "POST", resource_path: "/issue", request_body: { fields: { project: { key: "${inputs.project_key}" }, summary: "${inputs.summary}", description: "${inputs.description}", issuetype: { name: "Task" }, }, }, headers: { "Content-Type": "application/json", }, }), )
step.insert()
Script Step
// Create script step (ES5 ONLY!) var scriptStep = new GlideRecord("sn_ih_step_config") scriptStep.initialize()
scriptStep.setValue("action", actionSysId) scriptStep.setValue("name", "Process Response") scriptStep.setValue("order", 200) scriptStep.setValue("step_type", "script")
// Script (ES5 ONLY!) scriptStep.setValue( "script", "(function execute(inputs, outputs) {\n" + " // Get REST response from previous step\n" + " var response = inputs.rest_response;\n" + " \n" + " if (response.status_code === 201) {\n" + " var body = JSON.parse(response.body);\n" + " outputs.ticket_id = body.id;\n" + " outputs.ticket_url = body.self;\n" + " outputs.success = true;\n" + " } else {\n" + " outputs.success = false;\n" + ' outputs.error_message = "Failed: " + response.status_code;\n' + " }\n" + "})(inputs, outputs);", )
scriptStep.insert()
Subflows for Reuse (ES5)
Create Integration Subflow
// Subflows encapsulate reusable integration logic // Created via Flow Designer UI, but can be invoked via script
// Invoke subflow from script (ES5 ONLY!) var inputs = { ticket_id: "INC0010001", action: "update", fields: { status: "resolved", resolution: "Fixed", }, }
// Start subflow sn_fd.FlowAPI.startSubflow("x_myapp_update_external_ticket", inputs)
Execute Action from Script
// Execute spoke action from script (ES5 ONLY!) var actionInputs = { summary: "New ticket from ServiceNow", description: "Created via integration", priority: "Medium", }
try { var result = sn_fd.FlowAPI.executeAction("x_myapp_itsm.create_external_ticket", actionInputs)
if (result.outputs.success) { gs.info("Created ticket: " + result.outputs.ticket_id) } else { gs.error("Failed: " + result.outputs.error_message) } } catch (e) { gs.error("Action execution failed: " + e.message) }
Error Handling (ES5)
Action Error Handling
// Error handling in action script (ES5 ONLY!) ;(function execute(inputs, outputs) { try { // Main logic var response = callExternalAPI(inputs)
if (response.status_code >= 400) {
throw new Error("API error: " + response.status_code + " - " + response.body)
}
outputs.result = JSON.parse(response.body)
outputs.success = true
} catch (e) { outputs.success = false outputs.error_code = "INTEGRATION_ERROR" outputs.error_message = e.message
// Log for debugging
gs.error("IntegrationHub action failed: " + e.message)
// Optionally throw to trigger Flow Designer error handling
// throw e;
} })(inputs, outputs)
Retry Logic
// Retry wrapper for transient failures (ES5 ONLY!) function executeWithRetry(fn, maxRetries, delayMs) { var attempts = 0 var lastError = null
while (attempts < maxRetries) { try { return fn() } catch (e) { lastError = e attempts++
if (attempts < maxRetries) {
gs.info("Retry " + attempts + " of " + maxRetries + " after error: " + e.message)
gs.sleep(delayMs * attempts) // Exponential backoff
}
}
}
throw new Error("Failed after " + maxRetries + " attempts: " + lastError.message) }
MCP Tool Integration
Available Tools
Tool Purpose
snow_query_table
Query spokes and actions
snow_find_artifact
Find integration configs
snow_test_rest_connection
Test connections
snow_execute_script_with_output
Test action scripts
Example Workflow
// 1. Find available spokes await snow_query_table({ table: "sn_ih_spoke", query: "active=true", fields: "name,label,vendor,version", })
// 2. Get spoke actions await snow_query_table({ table: "sn_ih_action", query: "spoke.name=Jira Spoke", fields: "name,label,description,category", })
// 3. Test connection await snow_test_rest_connection({ connection_alias: "x_myapp_jira_connection", credential_alias: "x_myapp_jira_credential", })
Best Practices
-
Connection Aliases - Abstract connection details
-
Credential Security - Never hardcode credentials
-
Error Handling - Graceful failure handling
-
Retry Logic - Handle transient failures
-
Logging - Comprehensive debug logging
-
Testing - Test with mock data first
-
Versioning - Track spoke versions
-
ES5 Only - No modern JavaScript syntax