Skip to content
cascadehook.docs
Receiver examples

Node.js receiver

Start a signed webhook receiver with Node.js 22 or later.

Save receiver-core.mjs and node-receiver.mjs in the same directory.

export CASCADEHOOK_SIGNING_SECRET='YOUR_ENDPOINT_SIGNING_SECRET'
node node-receiver.mjs

Configuration

The receiver listens on http://127.0.0.1:9000/webhooks. Set HOST and PORT if another listener is needed. Put it behind a public HTTPS host or reverse proxy and register that HTTPS URL in Cascadehook.

Set RECEIPTS_FILE to choose the persistent inbox path. Restart the receiver after installing or changing CASCADEHOOK_SIGNING_SECRET.

Connect the endpoint

Once the receiver is running, verify ownership and enable delivery. Then publish a new event matching its subscriptions and confirm a successful delivery.

Adapter source

node-receiver.mjs
import { createServer } from "node:http";
import { Readable } from "node:stream";
import { createReceiver } from "./receiver-core.mjs";

const receive = createReceiver({
  secret: process.env.CASCADEHOOK_SIGNING_SECRET,
  receiptsFile: process.env.RECEIPTS_FILE,
});
const port = Number(process.env.PORT ?? 9000);
if (!Number.isInteger(port) || port < 0 || port > 65535)
  throw new Error("Invalid PORT.");

const server = createServer(
  { requestTimeout: 15_000, headersTimeout: 10_000 },
  async (incoming, outgoing) => {
    try {
      const headers = new Headers();
      const critical = new Set([
        "webhook-id",
        "webhook-timestamp",
        "webhook-signature",
      ]);
      const seen = new Set();
      for (let i = 0; i < incoming.rawHeaders.length; i += 2) {
        const name = incoming.rawHeaders[i].toLowerCase();
        if (critical.has(name) && seen.has(name)) {
          outgoing.writeHead(400);
          outgoing.end("Duplicate webhook header.");
          return;
        }
        seen.add(name);
        headers.append(name, incoming.rawHeaders[i + 1]);
      }
      const bodyAllowed =
        incoming.method !== "GET" && incoming.method !== "HEAD";
      const request = new Request("http://receiver.local" + incoming.url, {
        method: incoming.method,
        headers,
        ...(bodyAllowed
          ? { body: Readable.toWeb(incoming), duplex: "half" }
          : {}),
      });
      const response = await receive(request);
      outgoing.writeHead(response.status, Object.fromEntries(response.headers));
      outgoing.end(Buffer.from(await response.arrayBuffer()));
    } catch {
      outgoing.writeHead(503);
      outgoing.end("Receiver unavailable.");
    }
  },
);

server.listen(port, process.env.HOST ?? "127.0.0.1", () => {
  console.info(
    "Receiver listening on http://127.0.0.1:" +
      server.address().port +
      "/webhooks",
  );
});

On this page