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.
Receive HCP Terraform run notifications on your laptop while you build, with a URL HCP Terraform can reach.
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 HCP Terraform workspace where you have admin access
- A Next.js app that uses the App Router and runs on port 3000
Start your app
For generic webhooks with a token, HCP Terraform adds an X-TFE-Notification-Signature header. It holds the HMAC-SHA512 digest of the request body, hex encoded. The key is your token. Read the raw body with request.text() and compare in constant time.
import { createHmac, timingSafeEqual } from "node:crypto";
export async function POST(request: Request) {
const token = process.env.TFE_NOTIFICATION_TOKEN;
if (!token) {
return new Response("Missing TFE_NOTIFICATION_TOKEN", { status: 500 });
}
const body = await request.text();
const received = request.headers.get("x-tfe-notification-signature") ?? "";
const expected = createHmac("sha512", token).update(body).digest("hex");
const receivedBuffer = Buffer.from(received);
const expectedBuffer = Buffer.from(expected);
const isValid =
receivedBuffer.length === expectedBuffer.length &&
timingSafeEqual(receivedBuffer, expectedBuffer);
if (!isValid) {
return new Response("Invalid signature", { status: 401 });
}
const payload = JSON.parse(body);
console.log(`Received HCP Terraform notification with keys: ${Object.keys(payload).join(", ")}`);
return new Response("ok", { status: 200 });
}Pick a token and store it in an environment variable. Use a random, high-entropy string. HCP Terraform doesn't show the token again after you save the notification, so keep it in .env.local.
TFE_NOTIFICATION_TOKEN=replace-with-a-long-random-stringStart the app on port 3000.
npm run devStart a tunnel
Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so your notification would point at a dead URL after a restart. Reserve it first on the Subdomains page. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:3000 -s my-terraform-appHORIZON: Tunnel connected
URL https://my-terraform-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-terraform-appYour public URL is https://my-terraform-app.hrzn.run. Keep this terminal open.
Add the notification in HCP Terraform
- Sign in to HCP Terraform and select your workspace.
- Select Settings, then Notifications.
- Select Create a Notification.
- For Destination, choose the generic webhook option. Slack, Microsoft Teams and email use other formats.
- Enter a Name.
- Set Webhook URL to
https://my-terraform-app.hrzn.run/api/webhooks/terraform. - Set Token to the same value as
TFE_NOTIFICATION_TOKEN. - Under Run Events, choose all events or pick specific ones: Created, Planning, Needs Attention, Applying, Completed and Errored.
- Select Create a notification.
When you save a notification that is enabled, HCP Terraform sends a verification request to your URL. Your handler must answer with a 2xx status. Keep npm run dev and the tunnel running, or the verification fails and HCP Terraform leaves the notification disabled.
The Token field is optional. Without it, HCP Terraform sends no X-TFE-Notification-Signature header and the handler above rejects every request, including the verification request.
Send a test event
- Open the notification on its detail page.
- Select the Send a Test link.
To see the result, select the Last Response box on the same page.
For a real event, queue a run in the workspace. HCP Terraform sends a notification for each run event you selected.
Check it works
After you save the notification or select Send a Test, your Horizon terminal prints one line:
POST 200 /api/webhooks/terraformYour app terminal prints the keys of the payload. On the notification page, the Last Response box shows the 200 response. If the line shows [401], see Troubleshooting.
Troubleshooting
The notification stays disabled
HCP Terraform enables a notification only after the verification request gets a 2xx response. It shows the error and keeps the notification disabled. Fix the handler, then toggle Enabled on the notification's detail page to verify again.
The signature doesn't match
The Horizon line shows [401]. Check these in order:
TFE_NOTIFICATION_TOKENis identical to the Token you entered, with no extra spaces or newline.- The handler uses SHA-512, not SHA-256, and a hex digest.
- The handler hashes the raw body. Don't run
JSON.parseandJSON.stringifyfirst, because that can change the bytes. - You restarted
npm run devafter you edited.env.local.
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-terraform-app and the URL stays the same. -s needs a subdomain you reserved, see Pricing.
Nothing reaches your app
- Check that the Horizon terminal is still running. If its last line is
Connection lost. Reconnecting…, wait forReconnected. - Check that the Webhook URL ends with
/api/webhooks/terraform. - HCP Terraform sends no notifications for speculative plans, or for workspaces with Local execution mode.
Next steps
- Read HashiCorp's workspace notifications page.
- See the notification configurations API for the payload and trigger names.
Test Sonatype Nexus Repository webhooks locally
Receive Sonatype Nexus Repository webhook events on localhost with a Horizon tunnel, and verify the X-Nexus-Webhook-Signature header.
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.