How-to guide
Verify a delivery on your receiver
When a queue signs its outbound deliveries, every request to your endpoint carries the same X-Queuey-* signature headers. Verify them so you only act on requests that genuinely came from Queuey — and reject replays.
The checks
- Read the signature headers; reject if any are missing.
- Reject stale timestamps (a few minutes of clock skew).
- Recompute the body hash from the raw bytes — never from a re-serialized JSON object.
- Rebuild the canonical string and HMAC-SHA256 it with your secret.
- Compare to
X-Queuey-Signaturein constant time.
Verify (TypeScript / Node)
import crypto from "crypto";
// Express-style handler. IMPORTANT: verify against the RAW body bytes,
// before any JSON parsing/re-serialization.
export function verifyQueueyDelivery(req, secret) {
const keyId = req.get("X-Queuey-Key-Id");
const timestamp = req.get("X-Queuey-Timestamp");
const nonce = req.get("X-Queuey-Nonce");
const signature = req.get("X-Queuey-Signature");
if (!keyId || !timestamp || !nonce || !signature) return false;
// 1. Reject stale deliveries (±5 minutes).
const skew = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!Number.isFinite(skew) || skew > 300) return false;
// 2. Recompute the body hash from the raw bytes.
const bodyHash = crypto.createHash("sha256")
.update(req.rawBody) // Buffer of the raw request body
.digest("hex").toLowerCase();
// 3. Rebuild the canonical string and HMAC it with your shared secret.
const canonical = [
req.method, // METHOD
req.path, // PATH
req.originalUrl.split("?")[1] ?? "", // QUERY
String(timestamp), // TIMESTAMP
nonce, // NONCE
bodyHash, // CONTENT_SHA256
].join("\n");
const expected = crypto
.createHmac("sha256", Buffer.from(secret, "utf8"))
.update(Buffer.from(canonical, "utf8"))
.digest("hex").toLowerCase();
// 4. Constant-time compare.
const a = Buffer.from(signature, "utf8");
const b = Buffer.from(expected, "utf8");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Verify the raw body
Frameworks that parse JSON before your handler runs will break verification — the bytes you hash must be exactly what Queuey sent. Capture the raw body (e.g.
express.raw() or a rawBody hook) and hash that.Related
- Signed requests (HMAC) — the same scheme, applied to requests into Queuey.
- Reliable delivery — use the Idempotency-Key to dedupe on your side too.