Webhooks

Trigger task execution from an external source.
A webhook is a publicly accessible URL that triggers a task execution. When a webhook is triggered, it passes its payload to the task through a webhook_payload JSON parameter.
Webhooks can be useful in a variety of scenarios, such as triggering task execution from external sources or integrating with third-party tools. For example, you can use webhooks to:
  • Create a new issue in your bug tracking software when a customer reports a bug
  • Deploy a new version of your application when your CI pipeline finishes building code
Adding a webhook to a task will create a publicly accessible URL. Anyone with the webhook URL will be able to trigger the task, so treat the URL as a secret.

Create a webhook

When a webhook is created, a unique secret URL is generated that is associated with a specific task and environment. A task can have multiple webhooks.
You can declare webhooks in your task definition file or add a webhook using the Studio. Webhooks are specified as a list of slugs, or as an object whose keys are the slugs and values are the webhook configuration.
typescript
Copied
1
// my_task.airplane.ts
2
export default airplane.task(
3
{
4
slug: "my_task",
5
webhooks: { my_webhook: { requireAirplaneToken: false } },
6
// Shorthand with no configuration:
7
// webhooks: ["my_webhook"]
8
},
9
async () => {...}
10
);

Get the webhook URL

Once your task is deployed, the webhook URL will be available on the task page:
You can paste the URL into any third-party tool that supports webhooks.

Trigger a webhook

You can trigger a webhook by making a POST request to the webhook URL. The request body is expected to be a JSON payload.
The payload is passed to the task as a JSON parameter with slug webhook_payload, which can be accessed in the task's code.
The webhook_payload parameter contains the following fields:
FieldDescription
slugThe webhook's slug, which is used to identify which webhook was triggered. This is useful if your task has multiple webhooks.
headersThe HTTP headers sent with the request, as an object.
bodyThe parsed request body, as an object.
rawBodyThe raw request body as a base64 encoded string. This is useful if you want to parse the body yourself or implement authentication that relies on the raw body.
You can then access the parameter in your task code. The exact syntax depends on the language you're using:
typescript
Copied
1
// my_task.airplane.ts
2
export default airplane.task(
3
{
4
slug: "my_task",
5
webhooks: { my_webhook: { requireAirplaneToken: false } },
6
},
7
async (params) => {
8
const { body } = params.webhook_payload;
9
// Do something with the body.
10
}
11
);

Testing

You can test your webhook by sending an HTTP POST request to the webhook's URL.
For example, if this payload is sent to the URL for the webhook with slug my_webhook via CURL command:
bash
Copied
1
curl https://api.airplane.dev/v0/webhooks/prod/tsk20230616zclw0nuh37q/my_webhook_secret \
2
-H "Content-Type: application/json" \
3
-d '{
4
"cats": { "name": "Mittens", "age": 5 }
5
}'
The task will receive this JSON value in the webhook_payload parameter:
JSON
Copied
1
{
2
"slug": "my_webhook",
3
"headers": {
4
"Content-Type": "application/json"
5
},
6
"body": {
7
"cats": { "name": "Mittens", "age": 5 }
8
},
9
"rawBody": "eyJjYXRzIjogeyJuYW1lIjogIk1pdHRlbnMiLCAiYWdlIjogNX19"
10
}
You can also test your webhook by pasting your test payload into the webhook_payload parameter on the task page, both in studio and once the task is deployed:

Authentication

By default, Airplane does not require authentication for webhooks. Anyone with the webhook URL can execute the underlying task.

Require an Airplane API key

If your upstream tool allows custom headers, you can configure your webhook to require an Airplane API key. If requireAirplaneToken is true, only requests with a valid API key in the X-Airplane-API-Key header will have permission to execute the task.

Custom authentication examples

You can implement custom authentication in your task logic to prevent unauthorized access. One common approach is to verify that a signature in the headers matches the body of the request. If you're comparing a signature against the rawBody, make sure to decode it from base64 before verifying the signature.

Stripe

Stripe recommends authenticating webhooks using a secret key. You can implement this in your task using the Stripe SDK and the rawBody from the webhook payload.
typescript
Copied
1
// stripe_webhook.airplane.ts
2
import airplane from "airplane";
3
import { Stripe } from "stripe";
4
5
export default airplane.task(
6
{
7
slug: "stripe_events",
8
name: "Stripe events handler",
9
envVars: {
10
STRIPE_API_KEY: {
11
config: "stripe_api_key",
12
},
13
STRIPE_WEBHOOK_SECRET: {
14
config: "stripe_webhook_secret",
15
},
16
},
17
webhooks: ["stripe_prod"],
18
// Webhooks adds a webhook_payload parameter for your task
19
},
20
async (params) => {
21
// Retrieve the Stripe API key and webhook secret from your environment variables
22
const apiKey = process.env.STRIPE_API_KEY;
23
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
24
25
// Create a new instance of the Stripe SDK using your API key.
26
const stripe = new Stripe(apiKey, { apiVersion: "2022-11-15" });
27
28
// Verify the webhook signature using the raw body.
29
// Retrieve the Stripe webhook signature from the headers
30
const { rawBody, headers } = params.webhook_payload;
31
const decodedBody = Buffer.from(rawBody, "base64").toString("utf-8");
32
const stripeSignature = headers["Stripe-Signature"];
33
try {
34
const event = stripe.webhooks.constructEvent(decodedBody, stripeSignature, webhookSecret);
35
// At this point, the webhook signature is valid
36
// You can now handle the event based on its type
37
switch (event.type) {
38
case "payment_intent.created":
39
// Handle payment intent created event
40
break;
41
// Add more cases for other event types as needed
42
default:
43
// Handle unrecognized event types
44
break;
45
}
46
return { body: "Webhook signature verified successfully" };
47
} catch (err) {
48
// An error occurred during signature verification
49
throw new Error("Webhook signature verification failed: " + err);
50
}
51
},
52
);

Linear

Linear hashes the webhook content using a SHA256 HMAC signature delivered in the Linear-Signature header. You can verify this signature in your task using the crypto module and the rawBody from the webhook payload.
For more information, see Linear's documentation.
typescript
Copied
1
import airplane from "airplane";
2
import crypto from "node:crypto";
3
4
export default airplane.task(
5
{
6
slug: "linear_events",
7
name: "Linear events handler",
8
envVars: {
9
// Create a webhook in the Linear UI: https://linear.app/settings/api
10
LINEAR_WEBHOOK_SECRET: {
11
config: "linear_webhook_secret",
12
},
13
},
14
webhooks: ["linear_issues"],
15
},
16
async (params) => {
17
// Retrieve the webhook secret from an environment variable.
18
const webhookSecret = process.env.LINEAR_WEBHOOK_SECRET;
19
if (!webhookSecret) {
20
throw new Error("Missing LINEAR_WEBHOOK_SECRET");
21
}
22
23
const {
24
webhook_payload,
25
}: {
26
webhook_payload: {
27
body: Record<string, any>;
28
headers: Record<string, any>;
29
rawBody: string;
30
};
31
} = params as any;
32
33
// Verify the webhook signature using the raw body.
34
const decodedBody = Buffer.from(webhook_payload.rawBody, "base64").toString("utf-8");
35
const signature = crypto.createHmac("sha256", webhookSecret).update(decodedBody).digest("hex");
36
if (signature !== webhook_payload.headers["Linear-Signature"]) {
37
throw new Error("Invalid signature");
38
}
39
40
// At this point, the webhook signature is valid.
41
// You can now handle the event based on its type.
42
switch (webhook_payload.body.type) {
43
case "Issue":
44
// Handle changes to Linear issues
45
break;
46
}
47
48
return params;
49
},
50
);

Reference

webhooks : { [slug: string]: { requireAirplaneToken?: boolean } }
slug
REQUIRED
string
A unique identifier for the webhook. This must be unique per task. This cannot be changed (a different slug will end up creating a new webhook URL).
Slugs must:
  • Be fewer than 50 characters long.
  • Use only lowercase letters, numbers and underscores.
  • Start with a lowercase later.
requireAirplaneToken
optional
Default
false
boolean

Whether an Airplane API Key is required to execute the webhook. If true, only requests with the X-Airplane-API-Key header will have permission to execute the task.