Talk to usGet started
Webhooks

Verify the signature

Every delivery carries Joomjoo-Signature with a timestamp and an HMAC-SHA256 of the timestamp, a dot, and the raw body, keyed with your endpoint's signing secret.

The header

Text
Joomjoo-Signature: t=1762182000,v1=5f1d2c...

t is the unix time of the delivery in seconds. v1 is HMAC_SHA256(secret, t + "." + rawBody) as lowercase hex. The same value is also sent as Mooj-Signature.

Verify

import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(parts.t + "." + rawBody).digest("hex");
  return expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    msg = parts["t"].encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
  • Compute over the raw bytes you received. Parsing and re-serialising the JSON changes the string and breaks the signature.
  • Compare in constant time.
  • Reject a t more than a few minutes old to shut out replays.
  • Answer 2xx only after the signature verified; anything else is retried.

Other headers

HeaderValue
Joomjoo-Event-TypeThe event type, the same as type in the body.
Joomjoo-Event-IdThe event id, the same as id in the body. Use it to deduplicate retries.
Mooj-Signature, Mooj-Event-Type, Mooj-Event-IdThe same three values under the older names.