SendGrid Event Webhook in Laravel (Receive, Queue, Normalize)
SendGrid Event Webhook in Laravel (Receive, Queue, Normalize)
This guide shows how to build a reliable SendGrid event webhook in Laravel: receive batched JSON events, ACK fast, dedupe, normalize, and forward to SendPromptly. You’ll implement an ack-fast endpoint, a queued worker that normalizes events, and an idempotency/dedupe strategy so retries and duplicates don’t create side effects.
This page uses the primary keyword sendgrid event webhook laravel and gives copy‑paste controller and job examples you can run locally.
What you get from SendGrid Event Webhook
SendGrid posts delivery and engagement events as a JSON batch to your endpoint. Understand what you receive so you can design stable consumers.
Event categories (deliverability vs engagement)
Deliverability: delivered, bounce, dropped, deferred. Engagement: open, click, spam report. Map each type to your internal routing and priority.
Payload shape (batched JSON array)
SendGrid sends a JSON array of event objects — parse tolerant and treat each item independently.
Retry behavior (why 2xx matters)
SendGrid retries non-2xx responses; returning 2xx quickly prevents duplicate retries and reduces downstream load.
Micro checklist:
- Expect a JSON array (batch) in the HTTP body.
- Return
2xxquickly to stop retries.- Persist raw payload for debugging and auditing.
Configure SendGrid
Configure which event categories SendGrid should post and where.
Where to set the Post URL + choose event types
Set the webhook URL in the SendGrid dashboard and select only the events you need.
Use “Test integration” safely in dev/stage
Use the dashboard test to validate your parsing path, but confirm end-to-end in staging with real traffic.
Recommended event selection for most apps (delivered, bounce, dropped, deferred, open, click)
Start with the core deliverability events plus opens/clicks if your product reacts to engagement.
Common gotcha: Test integration can succeed but miss production edge-cases (e.g., very large batches); always validate with real payloads.
Laravel endpoint design
Design the endpoint to be tolerant, fast, and queue work for background processing.
Route + controller structure
Keep the route simple and the controller minimal — parse and enqueue.
Parse batched events safely
Treat each array entry as a separate unit of work and validate required fields before queueing.
Always ACK fast (queue the heavy work)
Do not perform heavy work in the request thread — persist/queue and return 204 or 200 immediately.
Micro checklist:
- Validate that payload is an array before enqueueing.
- Log non-fatal parse errors and skip malformed items.
- Enqueue one job per event for predictable retries.
Required code snippets
A) Route
| |
B) Controller (ACK fast, queue processing)
| |
C) Job: normalize + forward to SendPromptly
| |
Idempotency + dedupe strategy
Design a stable event key and persist dedupe state before applying side effects.
Stable event key using sg_event_id + event name + timestamp
Combine provider IDs with event name/timestamp to form a stable dedupe key for idempotency checks.
Storage options (DB unique index vs Redis SET)
Use a DB unique index for long-term audit or Redis for short TTL dedupe; choose based on retention and replay needs.
Handling out-of-order events
Store occurred_at in your projected state and apply only if the incoming event is newer.
Micro checklist:
- Use
sg_event_idwhen present; fallback to hashed payload.- Persist dedupe key before processing.
- Emit
idempotent_hitlogs/metrics on duplicates.
Normalize events for SendPromptly
Map SendGrid fields into a canonical schema before posting to SendPromptly ingestion.
Mapping table (SendGrid → canonical)
Map email → recipient, sg_event_id → provider_id, timestamp → occurred_at, and keep provider meta for debugging.
Post to SendPromptly ingestion with an Idempotency-Key
Always include an Idempotency-Key header when posting to SendPromptly to avoid duplicate ingestion from retries.
Minimal test snippet:
1 2 3curl -i -X POST "https://YOUR-APP.test/api/webhooks/sendgrid/events" \ -H "Content-Type: application/json" \ -d '[{"email":"[email protected]","timestamp":1513299569,"event":"delivered","sg_event_id":"abc123"}]'
Try it with the Sample Project: Send one test event and confirm it shows up end-to-end. Open Sample Project
Observability
Store raw payloads and correlate provider message IDs to your internal IDs so you can trace retries and failures.
Store raw payload for debugging
Persist the raw JSON in webhook_inbox for replay and post-mortem analysis.
Correlate provider message IDs with your internal message IDs
Log sg_event_id and your internal dedupe key together so Message Log entries can be mapped to app traces.
Message Log workflow in SendPromptly
Use Message Log to inspect per-attempt timing, headers, and the normalized payload that arrived at SendPromptly.
Common gotcha: Not persisting raw payloads makes it hard to reproduce subtle parsing errors seen in Message Log.
Test steps (curl + expected response)
- Send a minimal batch
| |
Expected
HTTP/1.1 204 No Content- A queued job runs and your SendPromptly Message Log shows
email.provider_event(or your chosen canonical event name).
Common failure modes
- Non-2xx response → retries: SendGrid retries until it gets 2xx (rolling window up to ~24h).
- Assuming payload is a single object: SendGrid posts a JSON array (batch).
- Slow endpoint / timeouts: processing inline increases retry/duplicate risk; ACK fast and queue.
- No dedupe: duplicates happen; build idempotency around provider IDs (
sg_event_id). - Blocking IP allowlists: SendGrid IPs change; don’t rely on static IP allowlisting.
Related
- /docs/guides/email-provider-event-webhooks — Email provider event webhooks overview
- /docs/guides/sendgrid-signed-event-webhook-verification — Verify SendGrid signatures (Signed Event Webhook)
- /docs/event-ingestion/ — SendPromptly event ingestion endpoint
- /docs/webhooks/ — Webhook delivery model + retries
Conclusion
- ACK fast and queue the work; return
2xxto stop SendGrid retries. - Persist raw payloads and use
sg_event_idfor idempotency. - Normalize to a canonical schema before forwarding to SendPromptly.
- Use Message Log + correlation ids to debug deliveries.
- Test with both dashboard test events and real staging traffic.