How-to guide
Sign requests to Queuey (HMAC)
For higher-trust producers, replace the static API key with an HMAC signed request. Each request carries a signature over its method, path, timestamp, nonce, and body hash — so a captured request can't be replayed or tampered with.
1 — Required headers
X-Queuey-Key-Id: {keyId}
X-Queuey-Timestamp: {unixSeconds}
X-Queuey-Nonce: {uniqueNonce}
X-Queuey-Content-SHA256: {sha256HexOfRawBody}
X-Queuey-Signature: {signatureHex}2 — The canonical string
Build the string to sign by joining these six lines with a newline (\n), each value normalized exactly as below:
METHOD
PATH
QUERY
TIMESTAMP
NONCE
CONTENT_SHA256METHOD— the HTTP method, uppercase (POST).PATH— the request path exactly as sent, with any trailing slash trimmed (/events/ten_x/orders).QUERY— the query parameters flattened, sorted by key, URL-encoded, and joined with&; an empty string when there are none. This is not the raw query string. E.g.?b=2&a=1→a=1&b=2.TIMESTAMP— theX-Queuey-Timestampvalue (Unix seconds).NONCE— theX-Queuey-Noncevalue, trimmed.CONTENT_SHA256— the lowercase hex SHA-256 of the raw request body bytes.
3 — Sign it (HMAC-SHA256)
The signature is the lowercase hex HMAC-SHA256 of the canonical string with your signing secret:
import crypto from "crypto";
const secret = process.env.QUEUEY_SIGNING_SECRET!; // the HMAC signing key
const body = JSON.stringify({ eventType: "order.created", payload: { id: "10042" } });
const timestamp = Math.floor(Date.now() / 1000);
const nonce = crypto.randomUUID();
const bodyHash = crypto.createHash("sha256").update(body).digest("hex").toLowerCase();
const canonical = [
"POST", // METHOD (uppercase)
"/events/ten_yourTenant/orders", // PATH (as sent, no trailing slash)
"", // QUERY (normalized: flatten+sort+encode; "" if none)
String(timestamp), // TIMESTAMP (Unix seconds)
nonce, // NONCE (trimmed)
bodyHash, // CONTENT_SHA256 (lowercase hex sha256 of raw body)
].join("\n");
const signature = crypto
.createHmac("sha256", Buffer.from(secret, "utf8"))
.update(Buffer.from(canonical, "utf8"))
.digest("hex")
.toLowerCase();
// send X-Queuey-Key-Id / Timestamp / Nonce / Content-SHA256 / SignatureReplay protection
Queuey rejects a signed request whose timestamp is out of range or whose nonce has been seen before — so sign each request fresh with a new timestamp and nonce. Common rejection reasons:
invalid_timestamp, timestamp_out_of_range, invalid_signature.Related
- Verify deliveries — the reverse: check Queuey's signature on your receiver.
- Headers & status codes — the full header and response reference.