XM Functions
XM Functions enable you to execute server-side code.
Current Limitations
- Max execution time is 45 seconds
- Currently only supports Workflows Task Plugins
- Must use the prerelease local development pattern
- XM Functions perform best with consistent traffic and are limited in burst traffic scenarios. If you believe your use case will cause customers to breach 20 transactions per second (TPS), please let us know by clicking the Feedback & Support button in the lower right part of your screen and selecting Ask a Question.
Function File
module.exports = async function (context) {
// Replace with your code
return {}
}
The returned object of this function will become available to the task that called it.
Context
This object contains important and useful data and tools for the XM Function, including:
| Key | Type | Description |
|---|---|---|
arguments | object | The arguments passed into the XM Function. For Workflow Tasks, this corresponds to the arguments used in the Save Handler |
user | object | The data about the user that set up the task that is invoking the XM Function |
user.userId | string | The user ID for the user that set up the task that is invoking the XM Function |
user.brandId | string | The brand ID for the user that set up the task that is invoking the XM Function |
availableConnections | string[] | An array of the authentication connection names for this Extension |
client | object | Provides access to the functions that the XM Function can call. See the section below for more details |
Any piped text provided through context.arguments will be resolved automatically on the context object.
Client
The object that allows a developer to throw user errors and make outbound HTTP requests to external APIs. This client object is available to the function in the context, and can be referenced with the client variable name.
Send HTTP Requests
The fetch function sends an outbound HTTP request to a supplied API endpoint.
Parameters
| Key | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The URL to make a request to |
config | object | Yes | See details in subsequent rows |
config.method | string | Yes | The HTTP method (e.g. 'POST', 'GET, 'PUT', etc.) |
config.headers | string | Yes | Key/value pairs that correspond to headers to add to the request (e.g. "Content-Type": "application/json") |
config.body | object or string | No | The body of the request (e.g. a JSON set of key-value pairs) |
config.params | object | No | Query parameters of the request |
config.connection | object | No | Authentication to attach to the request |
config.connection.connectionName | string | Yes — if the connection property is defined in the config object | The name of the connection to use, retrieved from context.availableConnections |
config.connection.paramFormat | string | No | Where to place the authentication credential — header, body, or query |
config.connection.paramName | string | No | The value to use for the authentication credential key — As in, the field name for a body param, the query name for a query param, or the header name for a header. Ex: for a bearer token this might be "Authorization" |
config.connection.paramTemplate | string | Yes — if the connection property is defined in the config object | What to insert the authentication credential as. Use it similarly to a string passed to String.format, e.g. "Bearer %s" would be a template for bearer tokens, "%s" inserts the token as is |
timeout | number | No | Time in milliseconds that the service making the request should wait before returning a rejected promise. Defaults to 10000 milliseconds |
Return Value
A Promise that resolves to an object containing these two properties:
| Key | Type | Description |
|---|---|---|
status | number | the status code of the HTTP response |
responseData | unknown | the HTTP response body. This is automatically parsed if the response body is JSON |
If the HTTP response status is not 2xx it will still return a resolved Promise instead of a rejected one. The only time a Promise rejection will be returned is due to a failure to contact the requested endpoint, such as a network issue or an misconfiguration of the valid domains.
try {
const response = await context.client.fetch('https://example.com/my/api/route', {method: 'GET'});
// do stuff with response
return {
status: response.status,
data: response.responseData
};
} catch (error) {
return {
message: 'Something went wrong!'
err: error
};
}
// get the first available connection name
const connectionName = context.availableConnections[0]
context.client.fetch('https://example.com/my/other/api/route', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
myParam: 'myValue',
},
params: {
filter: 'random',
},
connection: {
connectionName: connectionName,
paramFormat: 'header',
paramName: 'Authorization',
paramTemplate: 'Bearer %s'
},
})
// ignore any response by not awaiting
Send HTTP Requests to the Qualtrics API
Invoke the qualtricsApiFetch function to make outbound HTTP requests to the Qualtrics Public API. This interface is equivalent to qualtricsApiFetch2 in the Plugin Client. Check out the details here.
// makes a request to GET /API/v3/surveys
try {
getSurveysResponse = await context.client.qualtricsApiFetch('surveys', {method: 'GET'});
return {
status: getSurveysResponse.status,
data: getSurveysResponse.responseData
};
} catch (error){
if (error.status >= 500){
// potentially implement retry logic here
} else {
return {
message: 'An unexpected error occurred',
status: error.status,
data: error.responseData
};
}
}
Throw User Errors
The throwUserError function throws special errors in the event of a user misconfiguration with throwUserError.
Workflows Task Plugin Example
When the throwUserError function is invoked in Workflows Task Plugins it causes the task to fail with a USER_CONFIGURATION_ERROR error in the execution result.
However, throwing an uncaught error within the worker.js differs in that it will fail the task with SERVICE_ERROR instead.
Parameters
| Name | Type | Description |
|---|---|---|
| message | string | The error message you want to display in the Task |
Returns
This function always throws an error, so it will never return.
if (context.userId != 'UR_test') {
// Fails the task with a USER_CONFIGURATION_ERROR
context.client.throwUserError("Invalid user configuration")
} else {
// Fails the task with a SERVICE_ERROR
throw new Error("Something went wrong!")
}
You can catch the error to ignore it, or handle it, and continue execution.
Write a Log Statement
Use log to write a log statement. If any logdrains have been configured for your XM Function, the logs will be exported to the configured HTTP Event Collector URL. Follow these directions in XMT to add logdrains to your XM Extension.
Parameters
| Name | Type | Required | Description | Notes |
|---|---|---|---|---|
level | string | Yes | The log level | Accepted values: "debug", "info", "warn", "error" |
message | string | Yes | The log message | Message must be under 8KB |
tags | Record<string, string> | No | Key value pairs to be added to the message |
Log Levels
The following table describes the different log levels with recommendations of when to use them. All levels are logged during execution, so these can be used however you see fit. However, utilizing log levels can make it easier to analyze logs generated by your function by allowing you to filter on different severity levels.
| Level | Description |
|---|---|
debug | Primarily utilized during development to help debug code execution |
info | Used for generic information or acknowledgements |
warn | Signals that something unexpected happened, but doesn't necessarily cause issues for the XM Function |
error | Indicates that something wrong or unexpected occurred during execution |
Returns
An empty Promise
context.client.log('info', 'Example log message', {'foo': 'bar', 'tag': 'value'})
/* Produces the following log statement:
<timestamp> app[xmfunction-<invocationId>]:
{
"applicationId": "<appId>", // ex: EXT_123
"brandId": "<brandId>", // ex: myorganization
"invocationId": "<invocationId>", // ex: XMF_123
"timestamp": "<ISO8601 timestamp>", // ex: 2023-02-09T03:56.32.409Z
"level": "<logLevel>", // ex: info
"message": "<message>", // ex: my log message
"tags": {"key": "value", ...} // { "foo": "bar" }
}
*/
Troubleshooting
I can't see log statements locally
If you have run xmt create logdrain --local and do not see any logs being emitted, check that you've done the following:
- Invoked your XM Function. If using XM Functions for Workflows Task, you can check the Workflow Run History to view past invocations.
- Shared the correct version of your XM Function to your brand. You can view the current version by navigating to the
Sharestab in the Developer Portal.
My logs aren't appearing in my HEC log collector
Double check the following:
- Make sure your logdrain is configured correctly. Run
xmt view logdrainsto list the configured logdrains for your XM Extension and verify the spelling and authentication of your collector endpoint. - Ensure your brand is included in the logdrain allow list. Run
xmt view logdrainsto list the configured logdrains for your XM Extension and verify that the correct brand(s) are included in the allow list. Only logs produced by the listed brands will be exported to the log collector. - Validate that your brand has the correct version of your XM Extension. You can view the current version by navigating to the
Sharestab in the Developer Portal.