When to use RESTMessageV2
RESTMessageV2 is appropriate when you need fine-grained control over the HTTP request from a script. For most Flow Designer integrations, use IntegrationHub spokes or the REST action — they handle authentication and error handling with less code. Use RESTMessageV2 directly in Script Includes when:
- The integration logic is complex and better expressed as code than as flow steps
- You are wrapping an API in a Script Include that flows or Business Rules call
- You need programmatic control over headers, parameters, or body construction
- You are building a reusable integration library for other developers to use
Basic GET request
var rm = new sn_ws.RESTMessageV2();
rm.setHttpMethod('GET');
rm.setEndpoint('https://api.example.com/v1/users');
rm.setRequestHeader('Authorization', 'Bearer ' + token);
rm.setRequestHeader('Content-Type', 'application/json');
rm.setRequestHeader('Accept', 'application/json');
var response = rm.execute();
var statusCode = response.getStatusCode();
var body = response.getBody();
if (statusCode == 200) {
var data = JSON.parse(body);
gs.log('Users returned: ' + data.users.length, 'APIIntegration');
} else {
gs.error('API GET failed: ' + statusCode + ' — ' + body, 'APIIntegration');
}
POST request with JSON body
var rm = new sn_ws.RESTMessageV2();
rm.setHttpMethod('POST');
rm.setEndpoint('https://api.example.com/v1/incidents');
rm.setRequestHeader('Content-Type', 'application/json');
rm.setRequestHeader('Authorization', 'Bearer ' + getToken());
var payload = {
title: current.getValue('short_description'),
severity: current.getValue('urgency'),
reference: current.getValue('number'),
assignee: current.assigned_to.getDisplayValue()
};
rm.setRequestBody(JSON.stringify(payload));
var response = rm.execute();
if (response.getStatusCode() == 201) {
var created = JSON.parse(response.getBody());
gs.log('External ticket created: ' + created.id, 'TicketSync');
current.setValue('u_external_id', created.id);
}
PUT request — full resource update
var rm = new sn_ws.RESTMessageV2();
rm.setHttpMethod('PUT');
rm.setEndpoint('https://api.example.com/v1/incidents/' + externalId);
rm.setRequestHeader('Content-Type', 'application/json');
rm.setRequestHeader('Authorization', 'Bearer ' + token);
rm.setRequestBody(JSON.stringify({
status: 'resolved',
resolution: current.getValue('resolution_notes'),
resolved_by: gs.getUser().getFullName(),
resolved_at: new GlideDateTime().getValue()
}));
var response = rm.execute();
gs.log('PUT status: ' + response.getStatusCode(), 'TicketSync');
PATCH request — partial update
var rm = new sn_ws.RESTMessageV2();
rm.setHttpMethod('PATCH');
rm.setEndpoint('https://api.example.com/v1/tickets/' + ticketId);
rm.setRequestHeader('Content-Type', 'application/json');
rm.setRequestHeader('Authorization', 'Bearer ' + token);
// Only send the fields that changed
rm.setRequestBody(JSON.stringify({ status: 'in_progress' }));
var response = rm.execute();
DELETE request
var rm = new sn_ws.RESTMessageV2();
rm.setHttpMethod('DELETE');
rm.setEndpoint('https://api.example.com/v1/subscriptions/' + subscriptionId);
rm.setRequestHeader('Authorization', 'Bearer ' + token);
var response = rm.execute();
if (response.getStatusCode() == 204) {
gs.log('Subscription deleted: ' + subscriptionId, 'SubSync');
}
Query parameters
var rm = new sn_ws.RESTMessageV2();
rm.setHttpMethod('GET');
rm.setEndpoint('https://api.example.com/v1/incidents');
// Query parameters in the URL
rm.setEndpoint('https://api.example.com/v1/incidents?status=open&priority=high&limit=50');
// Or use setQueryParameter()
rm.setQueryParameter('status', 'open');
rm.setQueryParameter('priority', 'high');
rm.setQueryParameter('limit', '50');
Named REST Messages — the reusable configuration approach
For integrations used in multiple places, create a named REST Message record at System Web Services > Outbound > REST Message. Define the base URL, authentication, and HTTP methods once — then call them by name from any script. This externalises credentials and endpoint URLs from code.
// Call a named REST Message with a named HTTP Method
var rm = new sn_ws.RESTMessageV2('Jira Integration', 'Create Issue');
// Set template variable values defined in the named message
rm.setStringParameterNoEscape('project_key', 'OPS');
rm.setStringParameterNoEscape('summary', current.getValue('short_description'));
rm.setStringParameterNoEscape('priority', getPriorityLabel(current.getValue('priority')));
var response = rm.execute();
if (response.getStatusCode() == 201) {
var result = JSON.parse(response.getBody());
current.setValue('u_jira_key', result.key);
}
Named REST Messages work with Connection and Credential Aliases — credentials are never in the code, only in the secure credential store.
Timeout configuration
// Set connection timeout (how long to wait to establish connection)
rm.setHttpTimeout(30000); // 30 seconds in milliseconds
// For slow external APIs, increase the timeout
rm.setHttpTimeout(60000); // 60 seconds
Asynchronous execution
Use executeAsync() when the REST call is triggered from a Business Rule or other synchronous context and you do not want to block the save operation:
// executeAsync returns immediately — response handled via callback
var rm = new sn_ws.RESTMessageV2('External API', 'Notify');
rm.setStringParameterNoEscape('incident_number', current.getValue('number'));
rm.executeAsync(); // Returns a response object for later inspection, does not block
// For true fire-and-forget from a Business Rule:
// Use an Async Business Rule, or better, a Flow Designer flow
For most async integration patterns, Flow Designer with a record trigger is cleaner than executeAsync() from a Business Rule. See Flow Designer vs Business Rules.
Response methods — reading what came back
var response = rm.execute();
// HTTP status code
var status = response.getStatusCode(); // e.g. 200, 201, 400, 500
// Response body as string
var body = response.getBody();
// Parse JSON response
var data = JSON.parse(body);
// Response headers
var contentType = response.getHeader('Content-Type');
var rateLimitRemaining = response.getHeader('X-RateLimit-Remaining');
// All headers as a map
var headers = response.getHeaders();
Production error handling pattern
Wrap every RESTMessageV2 call in try/catch — network failures throw exceptions, they do not return a response object:
function callExternalAPI(payload) {
try {
var rm = new sn_ws.RESTMessageV2();
rm.setHttpMethod('POST');
rm.setEndpoint(gs.getProperty('my.integration.endpoint', ''));
rm.setRequestHeader('Content-Type', 'application/json');
rm.setRequestHeader('Authorization', 'Bearer ' + getToken());
rm.setRequestBody(JSON.stringify(payload));
rm.setHttpTimeout(30000);
var response = rm.execute();
var status = response.getStatusCode();
var body = response.getBody();
if (status >= 200 && status < 300) {
return { success: true, data: JSON.parse(body) };
} else if (status == 429) {
gs.warn('Rate limited by external API', 'ExternalAPI');
return { success: false, error: 'rate_limited', retryable: true };
} else if (status >= 500) {
gs.error('External API server error: ' + status, 'ExternalAPI');
return { success: false, error: 'server_error', retryable: true };
} else {
gs.error('External API client error: ' + status + ' — ' + body, 'ExternalAPI');
return { success: false, error: 'client_error', retryable: false };
}
} catch (e) {
gs.error('RESTMessageV2 exception: ' + e.getMessage(), 'ExternalAPI');
return { success: false, error: 'network_error', retryable: true };
}
}
HTTP status code handling reference
For the complete HTTP status code reference and how to handle each one in ServiceNow integrations, see HTTP status codes in ServiceNow integrations.
Authentication patterns
// Bearer token (OAuth)
rm.setRequestHeader('Authorization', 'Bearer ' + accessToken);
// Basic auth (not recommended — use named REST message with credential alias instead)
var credentials = gs.base64Encode(username + ':' + password);
rm.setRequestHeader('Authorization', 'Basic ' + credentials);
// API Key header
rm.setRequestHeader('X-API-Key', apiKey);
rm.setRequestHeader('api-key', apiKey); // vendor-specific header names
// API Key in query parameter
rm.setQueryParameter('api_key', apiKey);
For OAuth 2.0 flows, see the OAuth 2.0 guide. For storing credentials securely, see Connection and Credential Aliases.
Related guides:
- ServiceNow REST API guide — calling ServiceNow's own REST API
- OAuth 2.0 guide — authentication for outbound calls
- Connection and Credential Aliases — secure credential storage
- HTTP status codes — handling all response codes
- IntegrationHub spokes — no-code REST integrations in Flow Designer
- MID Server guide — routing calls through internal networks