BetaThe V4 API is in beta — endpoints and functionality may change.

Webhook signatures

Verify that webhook deliveries are from Blooio with HMAC-SHA256.

Every webhook delivery includes an X-Blooio-Signature header. Verify it before trusting the payload.

Verifying a delivery: recompute the HMAC, compare in constant time, and reject stale deliveries. Click a step for details.

Header format

X-Blooio-Signature: t=1735324800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Part Meaning
t Unix timestamp (seconds) when Blooio sent the delivery
v1 HMAC-SHA256 hex digest of {timestamp}.{raw_body}

Signing secret

Creating a webhook returns a signing_secret once. Store it securely. Rotate with POST /webhooks/{webhookId}/secret/rotate if needed — rotation is immediate.

Verify (Node.js)

import crypto from "node:crypto";

function verifyBlooioSignature(rawBody, signatureHeader, secret) {
 const parts = Object.fromEntries(
 signatureHeader.split(",").map((p) => p.split("=")),
 );
 const timestamp = parts.t;
 const expected = parts.v1;
 const signed = crypto
 .createHmac("sha256", secret)
 .update(`${timestamp}.${rawBody}`)
 .digest("hex");
 const a = Buffer.from(signed, "utf8");
 const b = Buffer.from(expected || "", "utf8");
 if (a.length !== b.length) return false;
 if (!crypto.timingSafeEqual(a, b)) return false;
 // Reject stale deliveries (e.g. older than 5 minutes)
 const age = Math.abs(Date.now() / 1000 - Number(timestamp));
 return age < 300;
}

Use the raw request body string (not a re-serialized JSON object).