knadh/listmonk · error
error unmarshalling Sendgrid notification: %v
Error message
error unmarshalling Sendgrid notification: %v
What it means
Sendgrid's ProcessBounce expects the verified request body to be a JSON array of sendgridNotif objects. If json.Unmarshal fails — body isn't a JSON array, is malformed, or has fields with incompatible types — it returns 'error unmarshalling Sendgrid notification'.
Source
Thrown at internal/bounce/webhooks/sendgrid.go:60
}
pubKey, err := x509.ParsePKIXPublicKey(sigB)
if err != nil {
return nil, err
}
return &Sendgrid{pubKey: pubKey.(*ecdsa.PublicKey)}, nil
}
// ProcessBounce processes Sendgrid bounce notifications and returns one or more Bounce objects.
func (s *Sendgrid) ProcessBounce(sig, timestamp string, b []byte) ([]models.Bounce, error) {
if err := s.verifyNotif(sig, timestamp, b); err != nil {
return nil, err
}
var notifs []sendgridNotif
if err := json.Unmarshal(b, ¬ifs); err != nil {
return nil, fmt.Errorf("error unmarshalling Sendgrid notification: %v", err)
}
out := make([]models.Bounce, 0, len(notifs))
for _, n := range notifs {
if n.Event != "bounce" {
continue
}
typ := models.BounceTypeHard
if n.BounceClassification == "technical" || n.BounceClassification == "content" {
typ = models.BounceTypeSoft
}
tstamp := time.Unix(n.Timestamp, 0)
bn := models.Bounce{
CampaignUUID: n.CampaignUUID,
Email: strings.ToLower(n.Email),
Type: typ,View on GitHub (pinned to 670c01717d)
Solutions
- In SendGrid settings, set Event Webhook payload format to JSON, not the legacy form-encoded format
- Validate the body is a JSON array of objects, e.g. [{"email":"a@b.c","event":"bounce","timestamp":1700000000}]
- Log the raw body on failure and lint it as JSON
- Check that signature verification passed and the body wasn't altered in transit (any mutation breaks both verify and unmarshal)
Example fix
// before
{"email":"a@b.c","event":"bounce","timestamp":1700000000}
// after
[{"email":"a@b.c","event":"bounce","timestamp":1700000000}] Defensive patterns
Strategy: validation
Validate before calling
func validSendgridBody(b []byte) bool {
var probe []struct {
Email string `json:"email"`
Event string `json:"event"`
Timestamp int64 `json:"timestamp"`
}
return json.Unmarshal(b, &probe) == nil
}
// verify body parses as a JSON array before calling ProcessBounce Try / catch
bounces, err := handler.ProcessBounce(sig, ts, body)
if err != nil {
if strings.HasPrefix(err.Error(), "error unmarshalling Sendgrid notification") {
log.Printf("sendgrid raw body: %s", string(body))
http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
http.Error(w, "webhook error", http.StatusInternalServerError)
} Prevention
- Set SendGrid Event Webhook encoding to JSON (not the default form-encoded)
- Always test with a JSON array payload, even for a single event
- Log raw bodies on failures to detect proxies rewriting responses
- Keep SendGrid's event webhook pointed only at the bounce route
When it happens
Trigger: The SendGrid webhook endpoint receives a body that doesn't decode into []sendgridNotif: empty body, a single JSON object instead of an array, malformed JSON, form-encoded POST (SendGrid's default 'Event Webhook' POST vs JSON), or wrong types for timestamp (int64) or event fields.
Common situations: SendGrid Event Webhook is configured with default (form-encoded) payload format instead of JSON; testing with a single-object JSON body; a proxy injecting an HTML error page; SendGrid account sending non-bounce event batches with unexpected shapes.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- error unmarshalling postmark notification: %v
- error unmarshalling SNS notification: %v
- error unmarshalling SES notification: %v
- error asn1 unmarshal of signature: %v
- error parsing lang file: %s: %v
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/11255d88faad1427.
Report an issue: GitHub.