Skip to main content
Webhook signatures let you verify that a webhook notification was sent by mattilda Pay and that its content was not modified on the way to your endpoint. Verifying the signature protects you from malicious parties sending forged or tampered data to your webhook endpoint.
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 with whsec_. 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.
Store the secret in a secure place, such as a secrets manager or an environment variable. Never commit it to source control or expose it in client-side code.

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.
Where 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.
  1. Read the raw body of the request, before any JSON parsing.
  2. Parse the X-Signature header: split it by ,, then split each element by the first =. Take the value of t and collect all the v1 values in a list.
  3. Build the signed content by joining the timestamp, a . character, and the raw body: {t}.{raw_body}.
  4. Compute the HMAC-SHA256 of the signed content using your secret as the key, and encode it as lowercase hex.
  5. Compare your value with each v1 value using a constant-time comparison. The webhook is valid if at least one of them matches.
  6. Check that t is within your tolerance window (we recommend 5 minutes) to protect against replay attacks.
Always verify the signature against the raw body you received. Don’t parse the JSON and serialize it again: changes to whitespace, key order, or character escaping produce a different signature and the verification will fail.

Code examples

Test values

Use these values to check your implementation. Disable the timestamp check (or set your clock to t) 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 one v1 entry per secret, the new one first:
Because your verification accepts the webhook when any 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.
Don’t parse the header into a dictionary or map keyed by name. Repeated v1 keys would overwrite each other and you would only keep one of the signatures.

Best practices

  • Respond with a 2XX status code only after the signature is verified, and with a 4XX status 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 id to detect duplicates.