How HMAC webhook signing works
This page explains the HMAC-SHA256 signing scheme MapleGather uses on webhook deliveries — what it is, why it authenticates the sender, and how to verify a signature in your receiver.
The short version
Section titled “The short version”MapleGather signs every webhook delivery using HMAC-SHA256 with a secret known only to you and MapleGather. When your receiver verifies the signature, it proves the delivery came from MapleGather and has not been tampered with in transit. The signing secret is shown once when you create the endpoint — store it now. If you lose it, use Rotate secret (webhook_secrets_rotate) to issue a new whsec_… secret in place without recreating the endpoint.
How it works
Section titled “How it works”The signing secret is generated server-side when you create a webhook endpoint and returned in the create response exactly once. It is the HMAC key — you store it in your receiver’s environment as a secret; MapleGather never exposes it again in any API read response.
The signed headers are included on every POST delivery:
| Header | Contains |
|---|---|
X-Webhook-Signature |
HMAC-SHA256 hex digest of the signed string |
X-Webhook-Timestamp |
Unix timestamp (seconds) of when the delivery was dispatched |
The signed string is constructed as:
<X-Webhook-Timestamp>.<raw request body>That is: the timestamp value, a literal period (.), then the raw bytes of the request body (before any parsing).
Verification algorithm in your receiver:
- Read
X-Webhook-TimestampandX-Webhook-Signaturefrom the request headers. - Construct the signed string:
<X-Webhook-Timestamp>.<raw request body>. - Compute
HMAC-SHA256of the signed string using your stored signing secret (the key is raw bytes, not base64-decoded unless you stored it base64). - Hex-encode the digest and compare it to
X-Webhook-Signature. Use a constant-time comparison function (for example,hmac.compare_digestin Python,crypto.timingSafeEqualin Node.js) to prevent timing attacks. - If the comparison fails, return
400or403and do not process the payload. - Check that
X-Webhook-Timestampis within the last five minutes of the current UTC time. If it is older, reject the delivery. This is the replay-defense window.
Pseudo-code example:
import hmac, hashlib, time
def verify(secret: bytes, timestamp: str, body: bytes, signature: str) -> bool: signed_string = f"{timestamp}.".encode() + body expected = hmac.new(secret, signed_string, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, signature): return False if abs(time.time() - int(timestamp)) > 300: # 5 minutes return False return TrueWhy it works this way
Section titled “Why it works this way”HMAC authenticates the origin. Without signature verification, any server that knows your endpoint URL could post forged events — triggering false member creations, payment acknowledgements, or registrations in your system. The secret-bound HMAC means only MapleGather (which holds the same secret server-side) can produce a valid signature.
The five-minute timestamp window prevents replay attacks: a valid delivery captured in transit cannot be re-submitted hours later because the timestamp check will fail.
Edge cases & boundaries
Section titled “Edge cases & boundaries”- Replayed deliveries (via the delivery-log Replay action) regenerate the signature at the current timestamp. Your receiver should accept these — the replay timestamp is fresh, not the original.
- If you lose the signing secret, use Rotate secret (
webhook_secrets_rotate) to issue a newwhsec_…secret in place — the endpoint id, URL, and event subscriptions are all preserved. You do not need to delete and recreate the endpoint. - The signing secret is distinct per endpoint. Multiple endpoints have independent secrets.