Horizon

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.

app/api/webhooks/terraform/route.ts
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.

.env.local
TFE_NOTIFICATION_TOKEN=replace-with-a-long-random-string

Start the app on port 3000.

npm run dev

Start 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-app
Output
HORIZON: Tunnel connected
  URL          https://my-terraform-app.hrzn.run (reserved)
  Forwarding   http://localhost:3000
  Request log  https://hrzn.run/dashboard/tunnels/my-terraform-app

Your public URL is https://my-terraform-app.hrzn.run. Keep this terminal open.

Add the notification in HCP Terraform

  1. Sign in to HCP Terraform and select your workspace.
  2. Select Settings, then Notifications.
  3. Select Create a Notification.
  4. For Destination, choose the generic webhook option. Slack, Microsoft Teams and email use other formats.
  5. Enter a Name.
  6. Set Webhook URL to https://my-terraform-app.hrzn.run/api/webhooks/terraform.
  7. Set Token to the same value as TFE_NOTIFICATION_TOKEN.
  8. Under Run Events, choose all events or pick specific ones: Created, Planning, Needs Attention, Applying, Completed and Errored.
  9. 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.

Send a test event

  1. Open the notification on its detail page.
  2. 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:

Output
  POST    200  /api/webhooks/terraform

Your 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_TOKEN is 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.parse and JSON.stringify first, because that can change the bytes.
  • You restarted npm run dev after 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 for Reconnected.
  • 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

On this page