Functions
Describes Optimizely Connect Platform (OCP) functions, which are stored in the src/functions directory for your app.
NoteA function in Optimizely Connect Platform (OCP) is a webhook.
In OCP, functions are small, executable blocks of code typically triggered by a real-time event. An app can have any number of functions stored in the src/functions directory. Common examples of functions include the following:
- Subscription to a list.
- Order purchase, refund, or cancellation.
- Marketing consent update survey submission.
- Product review or rating.
Typically, functions are triggered by an HTTP request (a webhook), although you can also use functions to populate app settings form fields. This document covers functions for handling webhooks. For more information about how to use functions as a data source in app settings forms, see Remote select options.
Define functions
Define functions in your app's app.yml file.
functions:
event_handler: #<-- name used for form sources and reference within the app
entry_point: Event #<-- File and Class name from src/functions folder
description: Processes incoming eventsImplement a function
The following are the requirements for any functions you define in your app.yml file:
- Add the function's code in the
src/functionsdirectory. For more information, seesrc/functions. - The file and class name must match the
entry_pointdefined in theapp.ymlfile. - The class must extend the
Functionclass from the@zaiusinc/app-sdkpackage and implement theperformmethod.
import * as App from '@zaiusinc/app-sdk';
export class Event extends App.Function {
public async perform(): Promise<App.Response> {
// processing logic here
return new App.Response(200);
}
}Function execution (default)
An app is installed for a specific OCP account, and when executing a function, you need to execute the function in the correct installation context for the account.
The default method to resolve the installation context is to use a GUUID that is contained within the public URL. All functions using the default installation resolution method (GUUID), defined in the app.yml file, are exposed using a public URL of the following format:
https://[REGIONAL_DOMAIN]/[APP_ID]/[FUNCTION]/[UUIDv4]
The event_handler function, defined in the Define functions section, uses the default installation resolution and therefore has a public URL that contains the GUUID to identify the installation context on function execution. You can retrieve the public URL using the following CLI command:
$ ocp directory list-functions app_id account_tracker_id
Usually, the default GUUID installation resolution method is sufficient to achieve most tasks and to integrate with third-party services.
Function execution (fixed URL)
In some cases, there is a need to have a fixed URL that does not change on a per-installation basis, like the GUUID-based URLs described previously. This requirement most likely arises due to a third-party service that, for example, only lets you define one URL for all function calls it makes to your OCP app.
To enable this scenario, you can specify an installation_resolution strategy when defining your function.
installation_resolution: #<-- optional, default is GUUID
type: HEADER | QUERY_PARAM | JSON_BODY_FIELD
key: stringThe installation_resolution is an optional property that, if defined, lets you specify how a function should resolve the installation context. The URL is the same for all installations, but the function is resolved to the correct installation based on the installation_resolution property.
Taking the previous example, extend the function definition in the app.yml file as follows:
functions:
event_handler_header:
entry_point: Event
description: Processes incoming events
installation_resolution:
type: HEADER
key: x-ocp-api-keyThe value set for the x-ocp-api-key header is used to resolve the context of the correct account installation.
ImportantThe value following the key must always be the public API key of the account you want to resolve.
The following are the supported types of installation resolution:
HEADER– The function call is resolved to the installation context based on the value of the specified header. For example,https://function.zaius.app/my_app/event_handler_headerwith a header ofx-ocp-api-keyand a value oftrackerId.QUERY_PARAM– The function call is resolved to the installation context based on the value of the specified query parameter. For example,https://function.zaius.app/my_app/event_handler_query?apiKey=trackerId.JSON_BODY_FIELD– The function call is resolved to the installation context based on the value of the specified field in the JSON body. For example,https://function.zaius.app/my_app/event_handler_jsonwith key$.apiKeyuses the value found for theapiKeyfield in the JSON body({"apiKey": "trackerId", "data": [1,2,3]}). Thekeyfor theJSON_BODY_FIELDresolution must be a valid JSONPath expression and must point to a string value. Additionally, every request must contain the headerContent-Type: application/json.
ImportantIf you modify a global function to a function with installation resolution between app versions, without changing the name of the function, both the global function and function with installation resolution are deployed under the same public URL.
In case an incoming request matches the defined installation resolution type, key, and value from the app manifest, OCP only calls the function with installation resolution. If the request does not match the defined installation resolution attributes, OCP calls the global function.
You can use the same CLI command as previously documented to see the public URLs for the function and the required request attributes.
$ ocp directory list-functions ocp_shakedown vdl
https://function.staging.zaius.app/ocp_shakedown/event_handler/61258d4b-dc70-48cb-9e2a-************
https://function.staging.zaius.app/ocp_shakedown/event_handler_headers (required HTTP Header with name 'x-api-key' and the value set to public tracking id)
https://function.staging.zaius.app/ocp_shakedown/event_handler_json
(required JSON body field that matches JSONPath '$.apiKey' and the value set to public tracking id and HTTP Header set to 'Content-Type: application/json')
https://function.staging.zaius.app/ocp_shakedown/event_handler_query (required URL query parameter with name 'apiKey' and the value set to public tracking id)In the app code, you can get the URL using the App-SDK getEndpoints method.
import { functions } from '@zaiusinc/app-sdk';
let webhookURL = (await functions.getEndpoints())['event_handler']You should then register the URL with the source of the event. The process for registering the URL depends on the third-party service that sends events.
ImportantYou must define any publicly available functions (for example, webhooks) in the
app.ymlfile.
Functions have access to the HTTP request path, query parameters, headers, and body. For example, the base URL for a function may look like the following: https://function.staging.zaius.app/my_app/cart/5f57ecbb-ef28-4a25-9056-3ea98994e2a5/
You can support additional paths and query parameters added to your URL. For example, https://function.staging.zaius.app/my_app/cart/5f57ecbb-ef28-4a25-9056-3ea98994e2a5/update?cart-id=12345
The path and query parameters are accessible to your function, body, and headers of the HTTP request.
import * as OCP from '@zaiusinc/app-sdk';
export class Event extends OCP.Function {
public async perform(): Promise<App.Response> {
const path = this.request.path; // "/update"
const cartId = this.request.params['cart-id']; // "12345"
const body = this.request.bodyJSON || {}; // '{"request_id":"12345","products":[...], ...}'
const headers = this.request.headers; // { "Content-Type": "application/json", ... }
// processing logic here
return new App.Response(200);
}
}Function example
import * as App from '@zaiusinc/app-sdk';
import {logger} from '@zaiusinc/app-sdk';
import {z} from '@zaiusinc/node-sdk';
import {IncomingEvent} from '../data/IncomingEvents';
import {transformToCustomer} from '../lib/transformToCustomer';
/**
* Example event handler.
* Expects a request in the form:
* url: https://[webhook-url]/?email=<email>
* with a JSON body.
* Fires an ODP event and updates the customer's name in ODP
*/
export class HandleEvent extends App.Function {
/**
* Handle a request to the handle_event function URL
* this.request contains the request information
* @returns App.Response as the HTTP response
*/
public async perform(): Promise<App.Response> {
const email = this.request.params['email'] as string;
if (!email) {
return new App.Response(400, 'Missing required email parameter');
} else {
try {
const event = this.request.bodyJSON as IncomingEvent;
// TODO: transform your event data into ODP API calls
if (event.customer) {
await z.customer(transformToCustomer(event.customer));
}
await z.event({
type: event.type,
identifiers: {
email
},
data: {
action: event.action
}
});
// return the appropriate status or response
return new App.Response(200);
} catch (e) {
logger.error(e);
return new App.Response(500, `An unexpected error occurred: ${e}`);
}
}
}
}Updated about 2 months ago