Refillo API

n8n

An n8n workflow that pushes stock and receives a webhook when the nightly plan finishes.

HTTP Request node

Add an HTTP Request node. Method POST. URL https://app.refilloapp.com/api/v1/stock. Authentication: Generic, header auth. Name Authorization, value Bearer and the key, with a space between. Do not put the key in the URL. Headers: Content-Type application/json, and Idempotency-Key set to the execution id. Body: the JSON in the nightly export recipe.

Webhook node

Add a Webhook node and copy its production https URL into Refillo, Settings, API, Add endpoint. Tick The nightly plan finished. In a Code node, the first step is the signature check below, with the raw body. If it returns false, stop the workflow. The secret is the one Refillo showed once when you added the endpoint.

The check

This is the Node check. It is the file examples/typescript/verify-webhook.ts, which the test runs.

import { createHmac, timingSafeEqual } from "node:crypto";

/** The check the webhooks guide shows. Same rule as Refillo's own verifier. */
export function verifyWebhook(secret: string, header: string, rawBody: string, nowSeconds: number): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(nowSeconds - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

if (process.argv[1]?.endsWith("verify-webhook.ts")) {
  const secret = "whsec_example";
  const body = '{"id":"1"}';
  const t = 1_700_000_000;
  const mac = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
  if (!verifyWebhook(secret, `t=${t},v1=${mac}`, body, t)) throw new Error("expected a valid signature");
  if (verifyWebhook(secret, `t=${t},v1=${mac}`, "other", t)) throw new Error("expected a mismatch");
  console.log("verify_webhook ok");
}