Webhook Triggers
Webhooks are automated notifications dispatched by Apps when specific events occur. These notifications carry a payload and are directed to a distinct URL called the "target URL". Webhooks typically offer a quicker and more efficient alternative to polling.
Defining a Webhook Trigger#
Webhook Triggers are defined using the defineWebhookTrigger
function of the Rollout framework.
Webhook Triggers require the definition of three functions: subscribe
, unsubscribe
and handleEvent
. Each of these functions will receive the user's Credential.
Subscribing to Webhooks#
A webhook Trigger's subscribe
function is responsible for letting the Trigger's App know that an Automation is interested in receiving notifications on a certain topic. It is called whenever an Automation using the Trigger is created or after the user updates the inputs to the Trigger. If an error is thrown from within the subscribe
function, the webhook subscription is considered to have failed and the user will be prevented from creating or updating their Automation.
The subscribe
function returns an object containing "subscription data", which Rollout will persist. The subscription data should contain whatever information is needed to in order to unsubscribe from the webhook created in the subscribe
function, as well as any data needed to identify which Automations to create Trigger Events for when a webhook event is received.
The Trigger's inputParams
can be used in order to filter the events for which webhook requests are sent. For example, the user may want to set up their Automation to Trigger for when a new item of type "A" is created, but not when an item of type "B" is created.
Unsubscribing from Webhooks#
A Trigger's unsubscribe
function is responsible for letting the Trigger's App know that an Automation is no longer interested in receiving notifications on a given topic. It is called when an Automation is deleted or when its Trigger configuration is changed. If an error is thrown from within the unsubscribe
function, unsubscribing from the webhook is considered to have failed and the user will be prevented from deleting or modifying their Automation.
The unsubscribe
function will receive a subscription
as a parameter. The subscription's
data
property is the object returned by the subscribe
function. Typically, a value stored in the subscription data will be used to identify which webhook to delete when making a request to the App's API.
Handling Webhook Events#
Handling webhook events requires defining an http request handler in order to create an endpoint corresponding to the target URLs defined in the subscribe
function. The webhook endpoint should call the handleWebhookRequestJob
function, with the request payload and the Trigger's handleEvent
function as arguments.
Example Webhook Trigger#
Trigger Definition#
import { v4 as uuid } from "uuid";import {defineTriggerPayloadSchema,defineWebhookTrigger,z,} from "@rollout/framework";import { MyAppCredential } from "./auth";import { inputParamsSchema } from "./input";import { payloadSchema } from "./payload";type RequestPayload = {webhookUrlId: string;body: unknown;};export const trigger = defineWebhookTrigger<MyAppCredential, RequestPayload>()({name: "New Item Webhook Trigger",inputParamsSchema,payloadSchema,async subscribe({ credential, inputParams }) {// Create a uuid to use as part of a unique target urlconst webhookUrlId = uuid();const subscribeRes = await fetch("https://api.example.com/items/webhooks", {method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${credential.data.accessToken}`,},body: JSON.stringify({targetUrl: "https://example.rolloutapp.com/api/apps/example/",// Filter to new items that match the user's selected item type onlyitemType: inputParams.itemType,}),});// Subscribe request failed, throw an error so that we don't fail silentlyif (subscribeRes.status !== 200) {throw new Error("Failed to webhook subscription failed!");}const { id } = await subscribeRes.json();// Return the webhookUrlId so that we can find the relevant Automation later and the id returned by the// app's API so that we're able to delete the webhook subscription on unsubscribereturn {webhookUrlId,webhookId: id,};},async unsubscribe({ credential, subscription }) {// subscription.data is whatever was returned from subscribeconst { webhookId } = subscription.data;const unsubscribeRes = await fetch(`https://api.example.com/items/webhooks/${webhookId}`, {method: "DELETE",headers: {"Content-Type": "application/json",Authorization: `Bearer ${credential.data.accessToken}`,},body: JSON.stringify({targetUrl: "https://example.rolloutapp.com/api/apps/example/",itemType: inputParams.itemType,}),});// Unsubscribe request failed, throw an error so that we don't fail silentlyif (unsubscribeRes.status !== 200) {throw new Error("Failed to delete webhook!");}},async handleEvent({ requestPayload }) {const { webhookUrlId, data } = requestPayload;const liveAutomations = await getLiveAutomations({where: { trigger: { appKey: "example", triggerKey: "new-item" } },});const automation = liveAutomations.find((au) => au.trigger.subscription.data.webhookUrlId === webhookUrlId);return automation? [{automationId: automation.id,event: { payload: data },},]: [];});
HTTP Endpoint#
import { Elysia } from "elysia";import { triggers } from "./triggers";const router = new Elysia().post("/webhooks/:webhookUrlId",async ({ body, request, params }) => {if (request.headers.get("X-Webhook-Secret") !== "MY_SECRET") {throw error(401, "Invalid secret");}handleWebhookRequestJob({triggerDef: triggers["new-item-webhook-Trigger"],requestPayload: {webhookUrlId: params.webhookUrlId,data: body,},});return "OK";},{body: t.Object({id: t.String(),name: t.String(),}),});export const http = defineHTTPHandler(router.handle);