Test Airship webhooks locally
Receive Airship Real-Time Data Streaming webhook events on localhost with a Horizon tunnel, and protect the endpoint with a custom header secret.
Receive Airship events on your laptop while you build, with a public HTTPS URL Airship can reach.
Horizon has no Airship integration. Airship posts events to a public URL, and Horizon provides that URL.
Airship doesn't sign webhook requests. Its docs describe no signature header. The protection Airship documents is custom headers: you add a key and value in the webhook settings, and Airship sends them with each request. This guide uses one as a shared secret.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - An Airship project with Real-Time Data Streaming enabled. It is an add-on service, so contact Airship Sales if it is off.
- A Next.js app that uses the App Router and runs on port 3000
Start your app
Airship posts events as JSON. By default, one request holds an array of 10 event objects. Each object has fields such as id, occurred and device.
The handler checks a custom header against a secret you choose. It compares in constant time.
import { timingSafeEqual } from "node:crypto";
export async function POST(request: Request) {
const secret = process.env.AIRSHIP_WEBHOOK_SECRET;
if (!secret) {
return new Response("Missing AIRSHIP_WEBHOOK_SECRET", { status: 500 });
}
const received = Buffer.from(request.headers.get("x-webhook-secret") ?? "");
const expected = Buffer.from(secret);
const isValid = received.length === expected.length && timingSafeEqual(received, expected);
if (!isValid) {
return new Response("Invalid secret", { status: 401 });
}
const events = (await request.json()) as { id: string }[];
console.log(`Received ${events.length} Airship event(s)`);
return new Response("ok", { status: 200 });
}X-Webhook-Secret is a name this guide picked. Airship doesn't define it. Pick a long random value for the secret.
AIRSHIP_WEBHOOK_SECRET=replace-with-a-long-random-stringStart the app.
npm run devStart a tunnel
In a second terminal, open a tunnel on a subdomain you reserved.
hrzn tunnel http://localhost:3000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-appYour public URL is https://my-app.hrzn.run. Airship requires an HTTPS host, and every Horizon tunnel has one. Keep this terminal open.
Use -s. Without it, the subdomain is random and changes every run, so your Airship webhook would point at a dead URL after a restart. Reserved subdomains are a paid feature, see Pricing.
Add the webhook in Airship
- In the Airship dashboard, open the dropdown next to your project name, then select Settings.
- Under Project settings, select Partner Integrations.
- Select Webhook.
- Enter a name and description.
- Set Webhook request URL to
https://my-app.hrzn.run/api/webhooks/airship. - Under Custom headers, add the key
X-Webhook-Secretand the same value asAIRSHIP_WEBHOOK_SECRET. - Choose a Batch size. A single object per request is easiest to read while you build.
- Under Event types, check the events you want.
- Select Activate.
Airship warns that webhook integrations are not recommended for high volume applications. Slow processing can back up the event stream. Keep your handler fast.
Trigger an event
The Airship docs describe no test button or resend for webhook integrations. Cause a real event for an event type you selected, for example by opening a message on a test device that belongs to your project.
Check it works
The Horizon terminal prints one line per request:
POST 200 /api/webhooks/airshipYour app terminal prints Received 1 Airship event(s), or more if you picked a larger batch size.
Troubleshooting
The route returns 401
The Horizon line shows [401]. The header value didn't match.
- Check that the Custom headers key is
X-Webhook-Secretand the value is identical toAIRSHIP_WEBHOOK_SECRET, with no extra spaces. - Restart
npm run devafter you edit.env.local.
Nothing reaches your app
- Check that the integration shows as active under Partner Integrations.
- Check that you selected the event type you trigger.
- Check that the Webhook request URL ends with
/api/webhooks/airship. - Check that the Horizon terminal is still running.
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app and the URL stays the same. -s needs a subdomain you reserved, see Pricing.
Next steps
- Read Airship's guide to Real-Time Data Streaming.
- See Pricing to reserve a subdomain so your URL never changes.
Test HCP Terraform webhooks locally
Receive HCP Terraform (Terraform Cloud) notification webhooks on localhost with a Horizon tunnel, and verify the X-TFE-Notification-Signature header.
Test Intercom webhooks locally
Receive Intercom webhook notifications on localhost with a Horizon tunnel, answer the HEAD validation request, and verify X-Hub-Signature.