diff --git a/src/_data/sidenav/main.yml b/src/_data/sidenav/main.yml index e4d6d4a93a..aeb66c3a54 100644 --- a/src/_data/sidenav/main.yml +++ b/src/_data/sidenav/main.yml @@ -113,8 +113,8 @@ sections: title: What can you do with cloud source data? - path: /connections/sources/custom-domains title: Set up a custom domain proxy in Segment - - path: /connections/sources/custom-sources - title: Custom Sources + - path: /connections/sources/source-functions + title: Source Functions - path: /connections/sources/visual-tagger title: Visual Tagger - path: /connections/sources/what-are-cloud-sources @@ -129,8 +129,8 @@ sections: menu_icon: read-more - path: /connections/destinations/destination-filters title: Destination Filters - - path: /connections/destinations/custom-destinations - title: 'Custom Destinations: Functions' + - path: /connections/destinations/destination-functions + title: Destination Functions - section_title: Warehouses slug: connections/warehouses section: diff --git a/src/connections/destinations/destination-functions/images/editor.png b/src/connections/destinations/destination-functions/images/editor.png new file mode 100644 index 0000000000..8d37234ead Binary files /dev/null and b/src/connections/destinations/destination-functions/images/editor.png differ diff --git a/src/connections/destinations/destination-functions/images/error-logs.png b/src/connections/destinations/destination-functions/images/error-logs.png new file mode 100644 index 0000000000..a01dbf886a Binary files /dev/null and b/src/connections/destinations/destination-functions/images/error-logs.png differ diff --git a/src/connections/destinations/destination-functions/images/function-sidesheet.gif b/src/connections/destinations/destination-functions/images/function-sidesheet.gif new file mode 100644 index 0000000000..db1593beea Binary files /dev/null and b/src/connections/destinations/destination-functions/images/function-sidesheet.gif differ diff --git a/src/connections/destinations/destination-functions/images/visual.png b/src/connections/destinations/destination-functions/images/visual.png new file mode 100644 index 0000000000..f9f8b9bc53 Binary files /dev/null and b/src/connections/destinations/destination-functions/images/visual.png differ diff --git a/src/connections/destinations/destination-functions/index.md b/src/connections/destinations/destination-functions/index.md new file mode 100644 index 0000000000..d8e1f4ca03 --- /dev/null +++ b/src/connections/destinations/destination-functions/index.md @@ -0,0 +1,271 @@ +--- +title: 'Destination Functions' +--- + +> note "" +> **NOTE:** Functions are currently in developer preview. If you are interested in joining the developer preview, navigate to the Build page in your catalog [here](https://app.segment.com/goto-my-workspace/build/catalog). The use is governed by [(1) Segment First Access](https://segment.com/docs/legal/first-access-beta-preview/) and Beta Terms and Conditions and [(2) Segment Acceptable Use Policy](https://segment.com/docs/legal/acceptable-use-policy/). + +Destination Functions allow you to transform and annotate your Segment events and send them to any external tool or API with a few lines of JavaScript. You can even send Segment events to your internal APIs for further handling. + +Here are some examples of Destination Functions are being used by early adopters: + +- **Microsoft Teams** : trigger notifications/messages on a Teams workspace on important events like `Signed Up` or `Order Placed`. +- **ChargeBee** : sync subscription information by sending events like `Subscription Started`, `Subscription Updated`, `Subscription Removed` etc. +- **Typeform Surveys** : trigger a user dissatisfaction survey on Typeform when a user uninstalls your app, for example when an `App Uninstalled` event is fired. + +The illustration below explains how you might use a Destination Function: + +![](images/visual.png) +_When a page call is sent to Segment, Destination Functions transform the Segment event payload to match the destination's spec. The transformed Event is sent to the destination tool._ + +## Getting Started + +### Creating your Destination Function + +To create a Destination Function: + +1. In your Segment Workspace, go to the Catalog, and click the [Functions tab](https://app.segment.com/goto-my-workspace/functions/catalog). +2. Click **New Function**. +3. Select **Destination Function** and click **Build**. + +### Writing your function + +When you click **Build** button, a code editor opens so you can configure your destination logic. Segment provides templates that make it simple to send data to a JSON API or other common use cases. + +Start by replacing the generic endpoint provided with the API endpoint (URL) for your tool or internal service. + +![Functions Editor](images/editor.png) + +For each event sent to your Destination Function, Segment invokes your function based on the event type. (Unless prevented by [Destination Filters](https://segment.com/docs/connections/destinations/destination-filters/)). + +You can define and export functions for each type in the [Segment Spec](https://segment.com/docs/connections/spec/) that you want to handle: + +- `onIdentify` +- `onTrack` +- `onPage` +- `onScreen` +- `onGroup` +- `onAlias` +- `onDelete` + +Two arguments are provided to the function: the `event payload` and the `settings`. All Destination Functions have an `apiKey` setting by default. + +- The **Event** argument to the function is the [Segment Event Data](https://segment.com/docs/connections/spec/common/#structure) payload. + > **Note** Only Event Sources are supported at this time. Object Source data is not supported. +- The `Settings` argument to the function contains user settings like `apiKey` and any custom settings and secrets that you add (coming soon!). + +The Functions are ["async/await" style JavaScript](https://javascript.info/async-await), and use the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) in the pre-loaded `fetch` package for external requests. This ensures seamless integration with the Event Delivery tab in the Segment dashboard for your Destination. + +Here's a basic example of a function that POSTs the event to a "request bin" for introspection. You can go to [RequestBin](https://requestbin.com/) to create your own `endpoint` to experiment with. + +The JavaScript below builds a query string for the URL, sets a basic authentication header, and sends a JSON body: + +```js +const endpoint = "https://mywebsite.com/api" + +async function onTrack(event, settings) { + const url = new URL(endpoint); + url.searchParams.set("ts", event.timestamp); + + const res = await fetch(url.toString(), { + body: JSON.stringify(event), + headers: new Headers({ + "Authentication": 'Basic ' + btoa(`${settings.apiKey}:`), + "Content-Type": "application/json", + }), + method: "post", + }) + + return await res.json() // or res.text() to avoid parsing response as JSON +} +``` + +The function returns data to indicate a success. In the example above we simply return the request body. + +You can also `throw` an error to indicate a failure. In the above example, try changing the endpoint to `https://foo` and you'll see it throws a `FetchError` with the message `request to https://foo/ failed, reason: getaddrinfo ENOTFOUND foo foo:443` + +There are three pre-defined error types that you can `throw` to indicate that the function ran as expected, but data could not be delivered: + +- `EventNotSupported` +- `InvalidEventPayload` +- `ValidationError` + +Here are basic examples using these error types: + +```js +async function onGroup(event, settings) { + if (!event.company) { + throw new InvalidEventPayload("Company is required") + } +} + +async function onPage(event, settings) { + if (!settings.accountId) { + throw new ValidationError("Account ID is required") + } +} + +async function onAlias(event, settings) { + throw new EventNotSupported("alias is not supported") +} +``` + +If you do not supply a function for an event type, Segment throws an implicit `EventNotSupported` error. + + +### Runtime and Dependencies + +Destinations Functions are run using Node.js 10.x. The following dependencies are pre-installed in the function environment: + +#### lodash + +A modern JavaScript utility library delivering modularity, performance & extras. [See the lodash docs](https://lodash.com/docs/4.17.11). + +#### AWS + +The official Amazon Web Services SDK. [See the AWS docs](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/). + +#### Crypto + +The crypto module provides cryptographic functionality that includes a set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify Functions. [See Crypto docs](https://nodejs.org/dist/latest-v10.x/docs/api/crypto.html). + +#### Fetch API + +The Fetch API provides a JavaScript interface for accessing and manipulating parts of the HTTP pipeline, such as requests and responses. It also provides a global `fetch()` method that provides an easy, logical way to fetch resources asynchronously across the network. [See the Fetch API docs](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). + +##### `fetch()` + +The `fetch()` method starts the process of fetching a resource from the network, returning a promise which is fulfilled once the response is available. [See docs](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch). + +##### `Request` + +The [`Request` interface](https://developer.mozilla.org/en-US/docs/Web/API/Request) of the Fetch API represents a resource request. + +##### `Response` + +The [`Response` interface](https://developer.mozilla.org/en-US/docs/Web/API/Response) of the Fetch API represents the response to a request. + +##### `Headers` + +The [`Headers` interface](https://developer.mozilla.org/en-US/docs/Web/API/Headers) of the Fetch API allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing. A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs. + +##### `URL` + +The [`URL` interface](https://developer.mozilla.org/en-US/docs/Web/API/URL) is used to parse, construct, normalize, and encode URLs. It works by providing properties which allow you to easily read and modify the components of a URL. + +##### `URLSearchParams` + +The [`URLSearchParams` interface](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) defines utility methods to work with the query string of a URL. + +##### `atob()` + +The [`atob()` function](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob) decodes a string of data which has been encoded using base-64 encoding. + +##### `btoa()` + +The [`btoa()` method](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa) creates a base-64 encoded ASCII string from a binary string. + +### ️Settings and Secrets + +Settings allow you to pass different variables to your function so that you can use it across multiple sources which might have different configurations. + +For example, if we include an `settingKey` settings string, you can access this from your code using dot notation on the `settings` object as follows: + +```js +async function onRequest(request, settings) { + let settingValue = settings.settingKey; +} +``` + +You can include multiple setting types including strings, booleans, string arrays and string objects to support your use case. You can also mark a particular setting as being required and/or sensitive (encrypted), if needed. + +Common use cases for using multiple settings include: + +- **Configuration and dynamic mappings for user-configurable flow control in your Destination Function**. Create a Destination Function once and allow your users to configure instances of that function multiple times with custom settings. +- **Additional secrets**. This is for use cases like client credentials authentication models, or for when calling multiple external APIs, for example in enrichment workflows or when looking up stored user traits and identifiers by any `externalId` from Segment's Personas [Profile API](/docs/personas/profile-api). + +## Testing + +You can test your code directly from the Functions editor by entering a test event and clicking **Run** to make sure the function works as expected. + +In the debugger panel, check the two outputs. The **Error Message** and the **Logs**. + +- **Error Message** - This shows the error surfaced from your function. +- **Logs** - The raw log. Any messages to `console.log()` from the function appear here. + +## Creation & Deployment + +Once you've finished writing your Destination Function, click **Configure** to save and use the function. On the screen that appears, give the function a name, and optionally add useful details (these are displayed in your workspace). Click **Create Function** to finish and make your Destination Function available in your workspace. + +If you're editing an existing function, you can **Save** changes without changing the behavior of your deployed function. Alternatively, you can also choose to **Save & Deploy** to push changes to an existing function. + + +## Logs and Errors + +Your function may encounter errors that you missed during manual testing or you may intentionally throw your own errors in your code if, for example, the incoming event is missing required fields. If your function throws an error, execution is halted immediately and Segment captures the event, any outgoing requests/responses, any console logs you may have printed, as well as the error itself. Segment then displays the captured error information in the "Event Delivery" tab of your Destination in the Segment dashboard as a "Bad Request". You can use this tab to find and fix unexpected errors. + +![Destination Function error logs](images/error-logs.png) + +You can throw [an Error or custom Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) and you can also add additional helpful context in logs using the [`console` API](https://developer.mozilla.org/en-US/docs/Web/API/console). For example: + +```js +async function onTrack(event, settings) { + const userId = event.userId + + console.log("userId:", userId) + + if (typeof userId != 'string' || userId.length < 8) { + throw new Error("input user ID is invalid") + } + + console.log("valid userId:", userId) + + // ... +} +``` + +> warning "" +> **Warning:** Do not log sensitive data, such as personally-identifying information (PII), authentication tokens, or other secrets. You should especially avoid logging entire request/response payloads. The "Function Logs" tab may be visible to other workspace members if they have the necessary permissions. + +## Management + +### Permissions + +The permissions required to create and manage a Destination Function are separate from those required to enable it on a source. + +Currently, you must be a **Workspace Owner** to create, edit or delete a function. + +Once you create a Destination Function, you can connect it to any source in your workspace. You need to be a `Workspace Owner` or `Source Admin`. + +### Editing & Deleting + +If you are a **Workspace Owner**, you can manage your Destination Function from the [Functions tab](https://app.segment.com/goto-my-workspace/functions/catalog). Click the function to change, and the panel that appears allows you to connect, edit or delete your function. + +![Editing or deleting your Destination Function](images/function-sidesheet.gif) + + +### Monitoring your Destination Function. + +You can use [Destination Event Delivery](https://segment.com/docs/guides/destinations/how-do-i-check-if-data-is-successfully-being-delivered-to-my-destination/) to understand if Segment encounters any issues delivering your source data to destinations. Errors that the Function throws appear here. + +### Controlling what gets passed to your Destination Function. + +You can use [Destination Filters](https://segment.com/docs/connections/destinations/destination-filters/) or Privacy Controls to manage what events and, of those events, which event properties are sent to your Destination Function. + +## FAQs + +**Does Segment retry events?** + +Segment retries nine times over the course of four hours. This increases the number of attempts for messages, so we try to re-deliver them another 4 times after some backoff. Segment doesn't retry if your function returns a permanent error. + +**Are the events guaranteed to send in order?** + +No. Segment cannot guarantee the order in which the events are delivered to an endpoint. + +**Can I create a device-mode Destination?** + +Functions enable you to write and deploy Cloud-mode Destinations. We're in the early phases of exploration and discovery for supporting customer "web plugins" for custom device-mode destinations and other use cases, but this is unsupported today. + +**How do I publish a destination to the Segment catalog instead of my own workspace?** + +If you are a Partner, looking to publish your destination and distribute your App through Segment Catalog, visit the [Developer Center](https://segment.com/partners/developer-center/) and check out our [partner docs](/docs/partners). diff --git a/src/connections/sources/source-functions/images/drift_example.png b/src/connections/sources/source-functions/images/drift_example.png new file mode 100644 index 0000000000..863949931a Binary files /dev/null and b/src/connections/sources/source-functions/images/drift_example.png differ diff --git a/src/connections/sources/source-functions/images/editor.png b/src/connections/sources/source-functions/images/editor.png new file mode 100644 index 0000000000..55db209744 Binary files /dev/null and b/src/connections/sources/source-functions/images/editor.png differ diff --git a/src/connections/sources/source-functions/images/error-logs.png b/src/connections/sources/source-functions/images/error-logs.png new file mode 100644 index 0000000000..87ce6cf6df Binary files /dev/null and b/src/connections/sources/source-functions/images/error-logs.png differ diff --git a/src/connections/sources/source-functions/images/function-sidesheet.gif b/src/connections/sources/source-functions/images/function-sidesheet.gif new file mode 100644 index 0000000000..db1593beea Binary files /dev/null and b/src/connections/sources/source-functions/images/function-sidesheet.gif differ diff --git a/src/connections/sources/source-functions/images/webhook-capture.gif b/src/connections/sources/source-functions/images/webhook-capture.gif new file mode 100644 index 0000000000..0f87fc3486 Binary files /dev/null and b/src/connections/sources/source-functions/images/webhook-capture.gif differ diff --git a/src/connections/sources/source-functions/index.md b/src/connections/sources/source-functions/index.md new file mode 100644 index 0000000000..fc6481e79d --- /dev/null +++ b/src/connections/sources/source-functions/index.md @@ -0,0 +1,350 @@ +--- +title: Source Functions +--- + +> note "" +> **Note:** Functions are currently in developer preview. If you are interested in joining the developer preview, go to the Build page in your catalog [here](https://app.segment.com/goto-my-workspace/build/catalog). The use is governed by [(1) Segment First Access](https://segment.com/docs/legal/first-access-beta-preview/) and Beta Terms and Conditions and [(2) Segment Acceptable Use Policy](https://segment.com/docs/legal/acceptable-use-policy/). + +Source Functions allow you to gather data from any third-party applications without worrying about setting up or maintaining any infrastructure. Source Functions are small pieces of JavaScript code that you create, edit, and deploy in Segment to receive webhooks and generate Segment events or objects using the [Segment Spec](https://segment.com/docs/connections/spec/). + +Here are some ways that Segment customers are using Source Functions: + +- **Create a long-term-value (LTV) computed trait using Zuora subscription data**: A customer connected Zuora webhooks to a webhook source and sent that data into Personas, where they created a computed trait to calculate the LTV for the customer in real-time. +- **Start an on-boarding campaign when a customer is marked "Closed Won" in Pipedrive**: Another customer set up an automated on-boarding email campaign in Iterable that triggered when the sales lead closed in Pipedrive. +- **Combine mobile subscription revenue with web revenues**: A major media company connected mobile and web revenue data for the first time, which allowed them to analyze it together in Amplitude. + +Here is an example of how a Source Function could be used to handle a webhook from Drift. + +![Drift Source Function](images/drift_example.png) + +## Getting Started + +### Creating your Source Function + +To create a Source Function: + +1. From your Segement Workspace, to go the Catalog and click the [Functions tab](https://app.segment.com/goto-my-workspace/functions/catalog). +2. Click **New Function**. +3. Select **Source Function** and click **Build**. + +### Writing your Function + +When you click **Build** button, a JavaScript code editor opens so you can configure your source logic to transform a webhook payload into events or objects to be sent downstream. Segment provides templates that make it simple to ingest data from your upstream webhook and offer example code to help you get started. + +![Functions Editor](images/editor.png) + +You can access the payload from the `request` variable like this: + +```js +async function onRequest(request, settings) { + const requestBody = request.json() + const requestHeader = request.headers.get('Content-Type') + const requestParam = request.url.searchParams.get("timestamp") + + // ... +} +``` + +Note that your handler function must be `async`. + +The first `request` argument contains the incoming webhook request including headers and body. You access the request body through a `json()` or `text()` helper function, the request headers with the [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) interface, and the request URL and query parameters with the [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) interface. + +#### Sending Messages + +You can send messages to the Segment API using the `Segment` object: + +```js +async function onRequest(request, settings) { + Segment.identify({ + userId: '1234', + traits: { + traitName: 'Example Trait' + } + }) + + Segment.track({ + event: 'Event Name', + userId: '1234', + properties: { + propertyName: 'Example Property' + } + }) + + Segment.group({ + userId: '1234', + groupId: '1234', + traits: { + traitName: 'Example Trait' + } + }) + + Segment.set({ + collection: 'collection_name_plural', + id: 'object_id_string', + properties: { + propertyName: 'Example Property' + } + }) +} +``` + +The `Segment` module has `Segment.identify()`, `Segment.track()`, `Segment.group()`, `Segment.set()`, `Segment.page()`, `Segment.screen()` and `Segment.alias()` functions. These fill in defaults and validate the data you provide. You can call these functions anywhere in your function. + +Behind the scenes, the `Segment` module appends messages to an array and sends messages to the Segment [Tracking API](https://segment.com/docs/connections/sources/catalog/libraries/server/http/) and [Object API](https://segment.com/docs/connections/sources/catalog/libraries/server/object-api/), as long as your function returns without error. + +##### Events + +Events are used to trigger real-time workflows in downstream streaming destinations. These events are compatible with both streaming destinations and warehouses. Use the following return format for events from the function: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue TypeDescription
eventStringThe name of the event you want to fire. This is only valid for `Segment.track()`.
userIdStringThe user ID you want to associate the event with. If the type of the call is group then use the groupId.
groupIdStringThe Account or Group ID of the user. This is only valid for `Segment.group()`.
propertiesObjectsA list of key/value pairs that are sent as properties to the event. This is only valid for `Segment.track()`, `Segment.page()` and `Segment.()screen`.
traitsObjectsA list of key/value pairs that are sent as traits to the event. This is only valid for `Segment.identify()` and `Segment.group()`.
+ +For more details on the events that are supported, see the [HTTP](https://segment.com/docs/connections/sources/catalog/libraries/server/http/) and [Object](https://segment.com/docs/connections/sources/catalog/libraries/server/object-api/) API documentation. + +##### Objects + +Objects are pieces of data that you can ETL (extract, transform, load) to your warehouse. Objects are not compatible with streaming destinations. For more details about what is supported with objects, review the [Objects API documentation](/docs/connections/sources/catalog/libraries/server/object-api/). + + + + + + + + + + + + + + + + + + + + + + +
KeyValue TypeDescription
collectionStringThe collection translates to the name of the table in your warehouse. Examples: `products`, `rooms`, `leads`. Naming should be lower case and plural.
idStringThe unique object ID. Any future objects with the same Object ID are upserted, de-duped and merged.
propertiesObjectA list of key/value pairs that are sent as properties of the object. These translate to columns downstream in your warehouse.
+ +### Runtime and Dependencies + +Source Functions are run using Node.js 10.x. The following dependencies are pre-installed in the function environment: + +- atob v2.1.2 +- aws-sdk v2.488.0 +- btoa v1.2.1 +- form-data v2.4.0 +- lodash v4.17.15 +- node-fetch v2.6.0 +- oauth v0.9.15 +- xml v1.0.1 + +Additional dependences are not currently supported using the web based IDE, but we're looking to add them in the near future. Until then [contact us](https://segment.com/help/contact/) to request additional dependencies. + +### Settings and Secrets + +Settings allow you to pass configurable variables to your function. A common pattern is to configure settings for an API URL endpoint and secret API key, so that you can use the same code with different settings for different workspaces. + +For example, if we include an `settingKey` string setting you will be able to access this in your code using dot notation on the settings object as follows: + +```js +function onRequest(request, settings) { + let settingValue = settings.settingKey; +} +``` + +You can include multiple setting types including strings, booleans, string arrays and string objects to support your use case. You can also mark a particular setting as being required and/or sensitive (encrypted), if needed. + +Settings can help you build a function that can be reused without having to modify any code in the Function itself. For example, customers can use settings to: + +- Build a function that can be rolled out without code changes to various Shopify stores +- Source payment data from a payment process and have a setting to denote the region for that function + + +## Testing Your Function + +You can test your code directly from the Functions Editor in two ways: + +#### Webhook Catcher + +Start by copying the webhook URL from the sidebar or "Auto-fill via webhook" dialog to your upstream tool or service. This allows you to receive payloads which you can use to test your function code. + +![Capture events to test your function](images/webhook-capture.gif) + +Segment automatically listens to your webhook URL for any JSON events (for example `Content-Type: application/json`) which are triggered. + +Click **Run** to test the event against your function code. + +#### Manual Input + +You can also manually include your own JSON payload with relevant headers before you click **Run**. In this view, you also have the option to switch back to the webhook catcher by clicking **Auto-fill via Webhook**. + +## Creation & Deployment + +Once you finish writing your Source Function code, save the code and create the Function by clicking **Configure**. On the screen that appears, give the function a name, and optionally add useful details (these are displayed in your workspace). Click **Create Function** to finish and make your Destination Function available in your workspace. + +If you're editing an existing function, you can **Save** changes without changing the behavior of your deployed function. Alternatively, you can also choose to **Save & Deploy** to push changes to an existing function. + +## Logs and Errors + +Your function may encounter errors that you missed during manual testing or you may intentionally throw your own errors in your code if, for example, the incoming request is missing required fields. If your function throws an error, execution is halted immediately and Segment captures the incoming request, any console logs you may have printed, as well as the error itself. Segment then displays the captured error information in the "Errors" tab of your Source in the Segment dashboard. You can use this tab to find and fix unexpected errors. + +![Source Function Error Logs](images/error-logs.png) + +You can throw [an Error or custom Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) and you can also add additional helpful context in logs using the [`console` API](https://developer.mozilla.org/en-US/docs/Web/API/console). For example: + +```js +async function onRequest(request, settings) { + const requestBody = request.json() + const userId = requestBody.userId + + console.log("userId:", userId) + + if (typeof userId != 'string' || userId.length < 8) { + throw new Error("input user ID is invalid") + } + + console.log("valid userId:", userId) + + // ... +} +``` + +> warning "" +> **Warning:** Do not log sensitive data, such as personally-identifying information (PII), authentication tokens, or other secrets. You should especially avoid logging entire request/response payloads. We only retain the 100 most recent errors and logs for up to 30 days but the "Errors" tab may be visible to other workspace members if they have the necessary permissions. + +## Management + +### Permissions + +The permissions required to create and manage a Source Function are separate from those required to enable it on a source. + +Currently, you must be a **Workspace Owner** in order to create, edit or delete a function. + +Once the Source Function has been created, you can enable it as you would a normal source. You need to be a `Workspace Owner` or `Source Admin` to enable a Source Function. + +### Editing & Deleting + +If you are a **Workspace Owner**, you can manage your Source Function from the [Functions tab](https://app.segment.com/goto-my-workspace/functions/catalog). Click the function tile and the panel that appears will allow you to connect, edit or delete your function. + +![Editing or deleting your Source Function](images/function-sidesheet.gif) + + +## Legacy Functionality + +### Legacy Source Functions + +Source Functions created before September 11, 2019 were written with a different JavaScript interface than documented above, and were optionally managed with an experiment API and CLI tool. + +These interfaces are now deprecated, so we recommend re-creating and managing your functions with the latest interfaces. + +However we still support these legacy functions. + +**Accessing the webhook payload (Legacy)** + +You can access the body, headers and query parameters of the function through the following: + +```js +exports.processEvents = async (event) => { + event.payload.body; + event.payload.headers; + event.payload.queryParameters; +} +``` + +**Returning messages (Legacy)** + +```js +{ + events: [{ + type: 'identify', + userId: '1234', + traits: {} + }, + { + type: 'track', + event: 'Event Name', + userId: '1234', + properties: {} + }, + { + type: 'group', + userId: '1234', + groupId: '1234', + traits: {} + }], + objects: [{ + collection: 'rooms', + id: "String of Object ID", + properties: {} + }] +} +``` + +**Migrating Function Code (Legacy)** + +First change the function definition to `async function onRequest(request, settings) {}`, instead of `exports.processEvents = async (event) => {}`. + +Next: + +- Access the request body as `const requestBody = request.json()`, instead of `event.payload.body` +- access the request headers as `request.headers.get(HEADERNAME)`, instead of `event.payload.headers[HEADERNAME]` +- access the request query parameters as `request.url.searchParams.get(PARAMNAME)` instead of `event.payload.queryParameters[PARAMNAME]` + +Finally send data with calls to `Segment.track()`, `Segment.identify()`, `Segment.group()` and `Segment.set()` instead of returning an object with `events` and `objects` arrays. + +### Legacy Source Function Model + +Source Functions created before January 28, 2020 were created as one-off _instances_. This means that you create it once and it can be used within your workspace a single time. Any changes you make to the code of this Source Function automatically update for this single instance. + +This behavior has now been deprecated and all existing Source Functions have been moved to the new model. This means that when you create a Source Function, you're creating a _class_ which can be deployed once, and _instances_ of this class can be configured in your workspace multiple times. The _class_ is parameterized by the function settings. + +## FAQs + +**What is the retry policy for a webhook payload?** + +The webhook payload retries up to 5 times with an exponential backoff for the function in the event of a failure with the function. After 5 attempts, the message is dropped. + +**What is the maxium payload size for the incoming webhook?** + +The maximum payload size for an incoming webhook payload is 2MB. + +**What is the timeout for a function to execute?** + +The execution time limit is 1 second. + +**What does "ReferenceError: exports is not defined" mean?** + +You are using a deprecated style of writing functions. See the "Migrating Function Code (Legacy)" section.