Dev Guide
Dev GuideUser GuideGitHubNuGetDevCommunitySubmit a ticketLog In
GitHubNuGetDevCommunitySubmit a ticket

Functions

Describes Optimizely Connect Platform (OCP) functions, which are stored in the src unctions directory for your app.

πŸ“˜

Note

A function in OCP is a webhook.

In Optimizely Connect Platform (OCP), functions are small, executable blocks of code typically triggered off of a real-time event. An app can have any number of functions stored in the src/functions directory. Common examples of functions include:

  • Subscription to a list
  • Order purchase/refund/cancellation
  • Marketing consent update survey submission
  • Product review/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 application's app.yaml file, for example:

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 events

Implement a function

Below are the requirements for any functions you define in your app.yaml file:

Example function:

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

An app is installed for a specific Optimizely Data Platform (ODP) 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.yaml 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 command-line interface (CLI) command:

$ ocp directory list-functions ocp_shakedown vdl
https://function.staging.zaius.app/ocp_shakedown/event_handler/61258d4b-dc70-48cb-9e2a-************

Usually, the default GUUID installation resolution method is sufficient to achieve most tasks and to integrate with thirrd-party services.

However, 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 above. This requirement most likely arises due to a third-party service that, for example, only allows you to 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: string

The installation_resolution is an optional property that, if defined, allows you to 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 example above, extend the function definition in the app.yaml file as follows:

functions:
  event_handler_header: 
    entry_point: Event
    description: Processes incoming events
    installation_resolution:
      type: HEADER
      key: x-ocp-api-key

The value set for the x-ocp-api-key header is used to resolve the correct accounts installation context.

🚧

Important

The value found under the key must always be the public API key of the account to which 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_header with a header of x-ocp-api-key and a value of trackerId.
  • 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_json with key $.apiKey will use the value found for the apiKey field in the JSON body({"apiKey": "trackerId", "data": [1,2,3]}). The key for the JSON_BODY_FIELD resolution must be a valid JSONPath expression and must point to a string value. Additionally, every request must contain the header Content-Type: application/json.

🚧

Important

If 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 will be 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 will only call the function with installation resolution. If the request does not match the defined installation resolution attributes, OCP will call the global function.

You can use the same CLI command as above 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. For example:

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.

🚧

Important

You must define any publicly available functions (for example, webhooks) in the app.yml file.

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. For example:

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/response
        return new App.Response(200);
      } catch (e) {
        logger.error(e);
        return new App.Response(500, `An unexpected error occurred: ${e}`);
      }
    }
  }
}