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 url
const 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 only
itemType: inputParams.itemType,
}),
});
// Subscribe request failed, throw an error so that we don't fail silently
if (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 unsubscribe
return {
webhookUrlId,
webhookId: id,
};
},
async unsubscribe({ credential, subscription }) {
// subscription.data is whatever was returned from subscribe
const { 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 silently
if (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);