Verify SendGrid Signed Event Webhooks in Laravel
Verify SendGrid Signed Event Webhook
SendGrid’s Signed Event Webhook ensures the event origin and integrity. This guide shows how to verify the ECDSA signature in Laravel using X-Twilio-Email-Event-Webhook-Signature and the raw request bytes so you only process trusted events. The primary keyword sendgrid signed event webhook verification appears in the examples.
You will implement middleware that verifies the signature, enforces a timestamp window, and only forwards verified events into your queue.
What “Signed Event Webhook” protects
Understand what the signature defends against and why raw bytes matter.
Why you should verify origin + integrity
Signatures confirm the sender and that the payload wasn’t tampered with in transit.
Headers involved (signature + timestamp)
X-Twilio-Email-Event-Webhook-Signature and X-Twilio-Email-Event-Webhook-Timestamp are required for verification.
Raw bytes requirement (no JSON re-encoding)
Verification operates on the timestamp concatenated with the raw body; re-encoding JSON will break verification.
Common gotcha: Verifying after any middleware that mutates the request body will cause false negatives — verify early.
Enable Signed Event Webhook in SendGrid
Turn on Signed Event Webhook in the dashboard and copy the public key to your config.
Where to toggle it + generate keys
Enable the feature under Webhooks → Event Webhook and save the public verification key provided by SendGrid.
Copy/store the public verification key securely
Store the public key in a secure config (environment variable or secrets manager); do not commit it.
Gotcha: “Test integration” only tests signing after save
Remember to save changes before using the dashboard test; otherwise the test will not reflect the new key.
Micro checklist:
- Save the webhook configuration after uploading the public key.
- Rotate keys in a maintenance window and validate with Signed Event Webhook tests.
- Store the public key in config; never log it.
Verification algorithm (SendGrid’s model)
Follow SendGrid’s algorithm precisely: timestamp + raw payload, SHA256, and ECDSA verify using the public key.
Concatenate timestamp + raw payload
The signed payload is timestamp + raw_bytes (no separator).
SHA256 hash
The signature is verified over the SHA256 hash of the signed payload.
Base64 decode DER signature and verify with ECDSA public key
The header carries a base64-encoded DER signature — decode it and verify with OpenSSL.
Suggested diagram: Show
timestamp + raw_body→ hash → DER sig verification with ECDSA public key.
Laravel implementation options
You can implement verification as middleware (recommended) or validate in controllers if you prefer.
Middleware (recommended)
Place the verification middleware early in the pipeline so nothing mutates the raw body before verification.
Controller-first verification (acceptable)
If middleware is not possible, verify at the top of the controller before parsing the body.
Timestamp drift window + replay protection
Apply a small timestamp window (e.g., 5 minutes) and consider short-term replay caches for additional protection.
Micro checklist:
- Verify timestamp drift (±5 minutes).
- Reject malformed base64 signatures.
- Cache recent message IDs to reduce replay risk.
Laravel middleware example (exact)
| |
Apply middleware
| |
Now confirm the full chain: send one verified event, then inspect the normalized event in SendPromptly. Open Sample Project · Open Message Log
Forward verified events into SendPromptly
Only enqueue or forward events after signature verification passes; use Idempotency-Key on ingestion requests.
Only enqueue after verification passes
Processing should be gated by verification to avoid spoofed events entering your pipeline.
Idempotency key strategy
Use a stable provider event id as the idempotency key when forwarding to SendPromptly.
Common gotcha: Verifying after queueing means unverified payloads can reach downstream workers — verify first.
Test steps (curl + expected response)
- Missing signature headers (should fail)
| |
Expected: HTTP/1.1 401 with missing signature headers
- Local fixture success (dev-only)
- Add a tiny PHP script to sign payloads with your own ECDSA keypair, then verify with your matching public key (to validate your code path).
- Expected:
204 No Contentwhen headers are present and valid.
Common failure modes
- Verifying against parsed JSON instead of raw bytes (signature mismatch).
- Not including timestamp + payload in the signed content (wrong message).
- Ignoring timestamp drift → replay window too large.
- Public key formatting issues (missing PEM headers/newlines).
- Forgetting to click Save before testing signing (test request won’t validate).
Related
- /docs/guides/sendgrid-event-webhook-laravel — Build the SendGrid webhook endpoint
- /docs/guides/email-provider-event-webhooks — Provider events overview + normalization
- /docs/webhooks/ — Webhook retries, ACK patterns, and dedupe
Conclusion
- Verify SendGrid Signed Event Webhook using the timestamp + raw body and OpenSSL/ECDSA.
- Place verification early (middleware) and enforce timestamp drift.
- Only enqueue or forward verified events to SendPromptly.
- Test locally with your own keypair and confirm runs in Message Log.