Mixmax Webhook is available on all Mixmax plans except the Free plan. Please check our pricing page for more information.
When you add a Webhook action to a Rule, Mixmax gives it a signing secret. Mixmax uses this secret to sign every webhook it sends to your URL. The receiving system can then use the same secret to confirm the request came from Mixmax and wasn't altered along the way.
This applies to Webhook actions in your own Rules and in Workspace rules.
Checking the signature is optional. If your receiving system doesn't check it, your webhooks arrive and work exactly as before. The webhook payload hasn't changed, and the signature is sent in extra request headers that your system can ignore.
Copying your signing secret
When you add a new Webhook action to a Rule, the Signing secret field shows the full secret, with a Copy button next to it. A signing secret looks like this:
whsec_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v=
Copy the secret and store it somewhere safe. The secret becomes active once you save the Rule, so add it to your receiving system after saving.
After you save the Rule and reload the page, the secret is masked and only its last four characters are shown, for example whsec_••••••••••••••••••••U1v=. Mixmax doesn't show the full secret again. If you didn't copy it, rotate the secret to get a new one (see below).
Treat your signing secret like a password. Anyone who has it can create valid signatures for your endpoint, so don't share it in emails, chats, or screenshots.
Rotating your signing secret
Rotate your signing secret if you think it may have been exposed or if you've lost your copy.
Go to your Mixmax Rules page (or Workspace rules in the Admin center) and open the Rule.
Click the Rule's Webhook action.
Click Rotate.
Copy the new secret right away. Like the original, it's only shown once.
Update your receiving system with the new secret.
Rotating takes effect immediately. There's no overlap period. From the moment you rotate, webhooks are signed only with the new secret, and your receiving system will fail to verify them until it's updated. Rotate when a short gap is acceptable, and have the new secret ready to deploy.
For developers: verifying a webhook signature
This section is for whoever builds or maintains the system that receives your webhooks.
Signature headers
Every signed webhook includes three request headers:
X-Mixmax-Signature: the version prefixv1,followed by the base64-encoded signature.X-Mixmax-Timestamp: when Mixmax sent the webhook, as a Unix timestamp in seconds.X-Mixmax-Webhook-Id: a unique ID for this delivery attempt.
A signed webhook looks like this:
POST / HTTP/1.1
Host: receiver.example.com
Content-Type: application/json
X-Mixmax-Signature: v1,ExhKA+zjl+INOSK6+kfsJlzCtAGVmg/uo0L0SzD2vXY=
X-Mixmax-Timestamp: 1779895167
X-Mixmax-Webhook-Id: a6d82fb0-433c-4668-832c-b58ce3571811
{"userId":"68efb78daf567354ffbd921a","messageId":"ui-created-action-test","eventName":"opened"}
How to verify a webhook
Read the three headers above from the incoming request.
Take your signing secret, remove the
whsec_prefix, and base64-decode the rest. This is your key.Build the string to sign by joining the webhook ID, the timestamp, and the raw request body with periods:
webhookId.timestamp.rawBodyCompute an HMAC-SHA256 of that string using your key, and base64-encode the result.
Compare the result with the part of
X-Mixmax-Signatureafter thev1,prefix, using a constant-time comparison.
If they match, the webhook came from Mixmax and the body wasn't changed.
Test your verification code before you rely on it. If your endpoint keeps rejecting Mixmax's webhooks, for example because the signature check fails, Mixmax pauses the Rule. See the FAQ in Mixmax Webhook event structure.
Example in Node.js
const crypto = require('crypto');
function verify(rawBody, headers, secret) {
const signatureHeader = headers['x-mixmax-signature'];
const ts = headers['x-mixmax-timestamp'];
const id = headers['x-mixmax-webhook-id'];
if (!signatureHeader || !ts || !id) return false;
const [version, sig] = signatureHeader.split(',');
if (version !== 'v1' || !sig) return false;
const expected = crypto
.createHmac('sha256', Buffer.from(secret.replace(/^whsec_/, ''), 'base64'))
.update(`${id}.${ts}.${rawBody}`)
.digest('base64');
const a = Buffer.from(sig);
const b = Buffer.from(expected);
// timingSafeEqual throws if the two buffers differ in length, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Two things to watch out for
Verify against the raw body. Sign the exact bytes you received, not a re-serialized copy of the parsed JSON. Many frameworks parse the body before your handler runs, so you may need to configure your server to keep the raw body. Re-serializing the parsed JSON can change the key order or spacing, and the signature won't match.
Use a constant-time comparison. Comparing with === (or your language's equivalent) can leak information about the correct signature. Use a purpose-built function such as crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python, or hash_equals in PHP. Check that the two values are the same length first, because some of these functions (including crypto.timingSafeEqual) throw an error when they aren't.
Guarding against replayed and duplicate webhooks
Mixmax includes a timestamp in every webhook but doesn't set an expiry. To protect against someone capturing a valid webhook and sending it again later, reject webhooks whose X-Mixmax-Timestamp is older than a window you choose. Five minutes is a common choice.
X-Mixmax-Webhook-Id is unique per delivery attempt, not per event. If a delivery fails and Mixmax retries it, the retry has a new ID, timestamp, and signature, so you can't use the webhook ID to spot a retried event. To de-duplicate retries, use a stable identifier from the event payload that fits that event type. See Mixmax Webhook event structure for the payload of each event.
FAQ
Do I have to verify signatures?
No. Verification is optional. If the system or tool receiving your webhooks doesn't check signatures, it will keep working normally, and you don't need to do anything with the signing secret.
Did the webhook payload change?
No. The webhook body is exactly the same as before; only the request headers are new. See Mixmax Webhook event structure for the payload formats.
Does this apply to Incoming Webhooks?
No. Signing secrets apply to webhooks Mixmax sends from a Rule's Webhook action. To send data to Mixmax, see Incoming Webhooks.
My Webhook action doesn't show a signing secret. Why?
The action was created before signing secrets were introduced, and existing Webhook actions weren't given one. To get a signing secret, add a new Webhook action.
I lost my signing secret. Can Mixmax send it to me?
No. For your security, Mixmax shows the secret only once and doesn't show it again in the app or the API, and our Support team won't resend it. Rotate the secret to get a new one, then update your receiving system.
Can I choose my own signing secret?
Not from the Rule editor. Mixmax generates the signing secret for you.
Can I send an API key or other headers with my webhooks?
Yes. Open the Rule, click its Webhook action, then click HTTP Headers (optional) and enter the header name and value your receiving system expects, such as an Authorization header with your API key. Click + to add another header. Mixmax sends these headers with every webhook from that action, exactly as entered (variables aren't supported). Unlike the signing secret, header values aren't masked after you save, so anyone who can open the Rule can see them.


