Horizon

Test Trend Micro webhooks locally

Receive Trend Micro Cloud One Conformity webhook notifications on localhost with a Horizon tunnel, and verify X-TrendMicro-Signature.

Receive Trend Micro Cloud One Conformity notifications on your laptop while you build, with a URL Conformity can reach.

This guide covers Conformity, the Trend Micro product that ngrok's guide also uses. Trend Vision One has its own webhook feature, which this page doesn't cover.

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.
  • A Trend Micro Cloud One Conformity account
  • A Next.js App Router app

Start your app

Conformity signs payloads only when you set a Webhook Security Token. The token is optional. With it, Conformity sends the X-TrendMicro-Signature header. The value is the HMAC-SHA256 (a keyed hash) hex digest of the payload body, with your token as the key.

Trend Micro's example hashes JSON.stringify of the parsed body. The handler accepts the digest of the raw body or of the re-serialized body, so it works in both cases. It compares in constant time, as Trend Micro does.

app/api/webhooks/trendmicro/route.ts
import { createHmac, timingSafeEqual } from "node:crypto";

const hexDigest = (token: string, content: string) =>
  createHmac("sha256", token).update(content).digest("hex");

const matches = (received: string, expected: string) => {
  const receivedBuffer = Buffer.from(received);
  const expectedBuffer = Buffer.from(expected);
  return (
    receivedBuffer.length === expectedBuffer.length &&
    timingSafeEqual(receivedBuffer, expectedBuffer)
  );
};

export async function POST(request: Request) {
  const token = process.env.CONFORMITY_SECURITY_TOKEN;
  if (!token) {
    return new Response("Missing CONFORMITY_SECURITY_TOKEN", { status: 500 });
  }

  const body = await request.text();
  const received = request.headers.get("x-trendmicro-signature") ?? "";

  let reserialized = body;
  try {
    reserialized = JSON.stringify(JSON.parse(body));
  } catch {
    return new Response("Invalid JSON", { status: 400 });
  }

  const isValid =
    matches(received, hexDigest(token, body)) ||
    matches(received, hexDigest(token, reserialized));

  if (!isValid) {
    return new Response("Invalid signature", { status: 401 });
  }

  console.log("Received Conformity notification:", JSON.parse(body));

  return new Response("ok", { status: 200 });
}

Store the token in an environment variable. You choose the token yourself in a later step. Conformity rejects tokens with non-base64 characters.

.env.local
CONFORMITY_SECURITY_TOKEN=replace-with-your-token

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

Your public URL is https://my-app.hrzn.run. Keep this terminal open. Conformity accepts only https:// URLs, and every Horizon tunnel serves HTTPS.

Add the webhook channel in Conformity

  1. In Conformity, go to the Main Dashboard and select your account.
  2. Open Settings, then Communication settings.
  3. Select Update communication settings and configure Webhook.
  4. Select Create a Webhook channel.
  5. Enter https://my-app.hrzn.run/api/webhooks/trendmicro as the Webhook URL. Conformity rejects localhost and IP addresses, so use the tunnel URL.
  6. Enter the same value as CONFORMITY_SECURITY_TOKEN in Webhook Security Token.
  7. Set automatic notifications and configure Triggers.
  8. Save the channel. It then appears in your Communication Settings.

Restart npm run dev if you changed .env.local after it started.

Trigger a notification

Conformity documents no test button and no resend for webhook channels. A notification arrives when one of your triggers fires. Set a broad trigger while you test, then cause that condition in your cloud account.

Check it works

When a trigger fires, your Horizon terminal prints one line:

Output
  POST    200  /api/webhooks/trendmicro

Your app terminal prints Received Conformity notification: followed by the payload. If the line shows [401], see Troubleshooting.

Troubleshooting

The signature doesn't match

  • Check that CONFORMITY_SECURITY_TOKEN is identical to the Webhook Security Token on the channel.
  • Restart npm run dev after you edit .env.local.
  • If you didn't set a token, Conformity sends no X-TrendMicro-Signature header and the handler rejects every request. Set a token on the channel.

Conformity rejects the webhook URL

Conformity accepts only https:// URLs. It rejects localhost and IP addresses. Use the https://my-app.hrzn.run tunnel URL.

No notification arrives

Conformity sends one only when a trigger fires. Check the triggers on the channel. Then check the Horizon request log to see whether a request reached the tunnel.

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

On this page