Verify Mailgun Webhook Signatures in Laravel
Verify Mailgun webhook signatures in Laravel
Validate Mailgun webhook authenticity with HMAC-SHA256 and single-use tokens to prevent replay. This guide gives a drop-in middleware example that verifies timestamp, token, and signature, enforces a timestamp window, and caches tokens to block replays.
The primary keyword mailgun webhook signature verification appears in the examples and test steps.
What Mailgun signs
Understand the fields Mailgun provides for verification so you use the correct key and inputs.
timestamp + token + signature fields
Mailgun sends timestamp, token, and signature fields; verification uses timestamp + token as the signed input.
Signing key (where it lives)
Use the webhook signing key shown in the Mailgun dashboard (not the account API key).
Micro checklist:
- Retrieve the webhook signing key from Mailgun dashboard.
- Store it in a secure config (env or secrets manager).
- Do not log the signing key or the raw signature.
Verification algorithm
Follow Mailgun’s algorithm exactly: concatenate timestamp+token (no separator), HMAC-SHA256, and constant-time compare.
Concatenate timestamp + token (no separator)
The input to HMAC is timestamp + token.
HMAC-SHA256 with your webhook signing key
Compute the HMAC using your webhook signing key and hex-encode the result.
Constant-time compare
Use hash_equals() to compare the expected and provided signatures.
Common gotcha: Using the API key instead of the webhook signing key or performing string comparisons with
==instead ofhash_equals().
Replay protection
Block token reuse and enforce a timestamp window to prevent replay attacks.
Cache token and reject repeats
Store the token in a short TTL cache (e.g., 10 minutes) and reject any repeat token submissions.
Timestamp drift window
Reject requests outside a small window (±5 minutes is typical).
Micro checklist:
- Cache tokens for single use (10 minutes).
- Enforce a 5-minute timestamp drift window.
- Log suspicious activity without storing secrets.
Laravel middleware implementation
Validate signature early and reject replays before queueing.
Validate early, before queueing
Put verification middleware near the top of the middleware stack so downstream code never processes unauthenticated payloads.
Log safely (never log signing key)
Log only safe context (message id, recipient); never include signature or token values in logs.
Required code snippets
A) Middleware
| |
Test steps (curl + expected response)
- Invalid signature
| |
Expected: 401 signature verification failed
- Valid signature (local)
- Compute signature:
| |
- Use that output as
signaturein curl → Expected:204 No Content
Mailgun’s official algorithm: concatenate timestamp+token (no separator), HMAC-SHA256 with your webhook signing key, compare hexdigest, and optionally cache tokens to prevent replay.
Common failure modes
- Using the wrong key (API key vs webhook signing key).
- Not using
hash_equals(timing leak). - No token caching → replay attacks possible.
- No timestamp drift window → accepts old replays.
- Verifying after parsing/mutating fields (verify on original field values).
Related
- /docs/guides/mailgun-webhooks-laravel — Receive Mailgun webhooks in Laravel
- /docs/guides/email-provider-event-webhooks — Normalize provider webhooks
- /docs/webhooks/ — Webhook verification + retries
See verified events in one place: forward only verified webhooks, then inspect delivery runs. Open Sample Project · Open Message Log
Conclusion
- Verify Mailgun webhook signatures using HMAC-SHA256 over
timestamp+token. - Cache tokens to prevent replay attacks.
- Use
hash_equals()and validate timestamp drift. - Place verification early and never log secrets.