{"record":{"id":"11255d88faad1427","repo":"knadh/listmonk","slug":"error-unmarshalling-sendgrid-notification-v","errorCode":null,"errorMessage":"error unmarshalling Sendgrid notification: %v","messagePattern":"error unmarshalling Sendgrid notification: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bounce/webhooks/sendgrid.go","lineNumber":60,"sourceCode":"\t}\n\n\tpubKey, err := x509.ParsePKIXPublicKey(sigB)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Sendgrid{pubKey: pubKey.(*ecdsa.PublicKey)}, nil\n}\n\n// ProcessBounce processes Sendgrid bounce notifications and returns one or more Bounce objects.\nfunc (s *Sendgrid) ProcessBounce(sig, timestamp string, b []byte) ([]models.Bounce, error) {\n\tif err := s.verifyNotif(sig, timestamp, b); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar notifs []sendgridNotif\n\tif err := json.Unmarshal(b, &notifs); err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling Sendgrid notification: %v\", err)\n\t}\n\n\tout := make([]models.Bounce, 0, len(notifs))\n\tfor _, n := range notifs {\n\t\tif n.Event != \"bounce\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ttyp := models.BounceTypeHard\n\t\tif n.BounceClassification == \"technical\" || n.BounceClassification == \"content\" {\n\t\t\ttyp = models.BounceTypeSoft\n\t\t}\n\n\t\ttstamp := time.Unix(n.Timestamp, 0)\n\t\tbn := models.Bounce{\n\t\t\tCampaignUUID: n.CampaignUUID,\n\t\t\tEmail:        strings.ToLower(n.Email),\n\t\t\tType:         typ,","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/sendgrid.go#L42-L78","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\n{\"email\":\"a@b.c\",\"event\":\"bounce\",\"timestamp\":1700000000}\n// after\n[{\"email\":\"a@b.c\",\"event\":\"bounce\",\"timestamp\":1700000000}]","handlingStrategy":"validation","validationCode":"func validSendgridBody(b []byte) bool {\n    var probe []struct {\n        Email     string `json:\"email\"`\n        Event     string `json:\"event\"`\n        Timestamp int64  `json:\"timestamp\"`\n    }\n    return json.Unmarshal(b, &probe) == nil\n}\n// verify body parses as a JSON array before calling ProcessBounce","typeGuard":null,"tryCatchPattern":"bounces, err := handler.ProcessBounce(sig, ts, body)\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"error unmarshalling Sendgrid notification\") {\n        log.Printf(\"sendgrid raw body: %s\", string(body))\n        http.Error(w, \"invalid payload\", http.StatusBadRequest)\n        return\n    }\n    http.Error(w, \"webhook error\", http.StatusInternalServerError)\n}","preventionTips":["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"],"tags":["webhook","json","unmarshal","sendgrid","go"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}