The payload is signed, not encrypted. You don’t need to decrypt anything: you recompute the
same signature with your secret and compare it with the one we sent.
Configuration
Signatures are computed with a signing secret shared between mattilda Pay and your merchant account. Secrets start withwhsec_.
To get a signing secret, contact the mattilda Pay support team. They will generate the secret for
your merchant account and share it with you through a secure channel. Once the secret is active,
every webhook we deliver to your endpoint includes the X-Signature header.
The X-Signature header
Each signed webhook is an HTTP POST with a JSON body (Content-Type: application/json) and the
following header.
The signature is computed as follows.
raw_body is the exact body of the HTTP request, byte for byte.
Every delivery attempt is signed again, so a retried webhook carries a new
t value and a
new v1 signature.Signature verification
To verify a webhook, follow these steps.- Read the raw body of the request, before any JSON parsing.
- Parse the
X-Signatureheader: split it by,, then split each element by the first=. Take the value oftand collect all thev1values in a list. - Build the signed content by joining the timestamp, a
.character, and the raw body:{t}.{raw_body}. - Compute the HMAC-SHA256 of the signed content using your secret as the key, and encode it as lowercase hex.
- Compare your value with each
v1value using a constant-time comparison. The webhook is valid if at least one of them matches. - Check that
tis within your tolerance window (we recommend 5 minutes) to protect against replay attacks.
Code examples
Test values
Use these values to check your implementation. Disable the timestamp check (or set your clock tot) while running this test.
The signature must match. Adding a single character to the body, or using a different secret,
must make the verification fail.
Secret rotation
You can request a new signing secret from the mattilda Pay support team at any time, for example if you suspect your secret was exposed. During a rotation both the new and the previous secret are active for a grace period. While both are active, every webhook is signed with each secret and the header contains onev1 entry per
secret, the new one first:
v1 matches, webhooks keep validating
with either secret. Update your endpoint to the new secret during the grace period, and let the
support team know so the previous secret can be retired.
Best practices
- Respond with a
2XXstatus code only after the signature is verified, and with a4XXstatus code when it isn’t. - Use a constant-time comparison function (
hmac.compare_digest,crypto.timingSafeEqual,hash_equals) to compare signatures. - Reject webhooks whose timestamp is outside your tolerance window.
- Process events idempotently: the same event can be delivered more than once, use the event
idto detect duplicates.