{"record":{"id":"ca2ece1cb335835d","repo":"knadh/listmonk","slug":"error-unmarshalling-sns-notification-v","errorCode":null,"errorMessage":"error unmarshalling SNS notification: %v","messagePattern":"error unmarshalling SNS notification: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bounce/webhooks/ses.go","lineNumber":85,"sourceCode":"// requests and bounce notifications.\ntype SES struct {\n\tmu    sync.RWMutex\n\tcerts map[string]*x509.Certificate\n}\n\n// NewSES returns a new SES instance.\nfunc NewSES() *SES {\n\treturn &SES{\n\t\tcerts: make(map[string]*x509.Certificate),\n\t}\n}\n\n// ProcessSubscription processes an SNS topic subscribe / unsubscribe notification\n// by parsing and verifying the payload and calling the subscribe / unsubscribe URL.\nfunc (s *SES) ProcessSubscription(b []byte) error {\n\tvar n sesNotif\n\tif err := json.Unmarshal(b, &n); err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling SNS notification: %v\", err)\n\t}\n\tif err := s.verifyNotif(n); err != nil {\n\t\treturn err\n\t}\n\n\t// Make an HTTP request to the sub/unsub URL.\n\tu := n.SubscribeURL\n\tif n.Type == \"UnsubscriptionConfirmation\" {\n\t\tu = n.UnsubscribeURL\n\t}\n\n\tresp, err := http.Get(u)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error requesting subscription URL: %v\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response on subscription URL: %v\", resp.StatusCode)","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/ses.go#L67-L103","documentation":"SES.ProcessSubscription handles SNS SubscriptionConfirmation/UnsubscriptionConfirmation messages. It first JSON-unmarshals the raw SNS POST body into sesNotif; if the body isn't valid JSON or fields have wrong types, it returns 'error unmarshalling SNS notification' before any signature verification.","triggerScenarios":"The SNS subscription endpoint receives a body that fails json.Unmarshal into sesNotif: empty body, HTML error page, malformed JSON, a JSON object that isn't an SNS envelope, or non-string values for fields like Timestamp/TopicArn.","commonSituations":"Pointing the SNS topic subscription at the wrong URL (another route's response body); testing the endpoint with hand-written or missing JSON; a load balancer returning an error page; SNS schema changes adding unexpected types; hitting the endpoint with the wrong HTTP method or content type.","solutions":["Log the raw request body on this error and lint it as JSON","Verify the SNS topic subscription points to the correct listmonk SES webhook URL with HTTP POST (SNS default content type)","Re-request the topic subscription so SNS sends a fresh SubscriptionConfirmation envelope","When testing manually, POST a realistic SNS envelope with Type, MessageId, TopicArn, Timestamp, SignatureVersion, Signature, SigningCertURL as strings"],"exampleFix":"// before\ncurl -X POST .../ses -d 'not json'\n// after\ncurl -X POST .../ses -H 'Content-Type: text/plain' -d '{\"Type\":\"SubscriptionConfirmation\",\"MessageId\":\"id\",\"TopicArn\":\"arn:aws:sns:us-east-1:1:t\",\"Timestamp\":\"2024-01-01T00:00:00Z\",\"SignatureVersion\":\"1\",\"Signature\":\"...\",\"SigningCertURL\":\"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-x.pem\",\"SubscribeURL\":\"https://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&...\"}'","handlingStrategy":"validation","validationCode":"func validSNSEnvelope(b []byte) bool {\n    var probe struct {\n        Type           string `json:\"Type\"`\n        MessageId      string `json:\"MessageId\"`\n        TopicArn       string `json:\"TopicArn\"`\n        Message        string `json:\"Message\"`\n        Signature      string `json:\"Signature\"`\n        SigningCertURL string `json:\"SigningCertURL\"`\n        Timestamp      string `json:\"Timestamp\"`\n    }\n    return json.Unmarshal(b, &probe) == nil && probe.Type != \"\" && probe.SigningCertURL != \"\"\n}\n// return 400 if !validSNSEnvelope(body)","typeGuard":null,"tryCatchPattern":"err := handler.ProcessSubscription(body)\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"error unmarshalling SNS notification\") {\n        log.Printf(\"SNS raw body: %s\", string(body))\n        http.Error(w, \"invalid SNS payload\", http.StatusBadRequest)\n        return\n    }\n    http.Error(w, \"webhook error\", http.StatusInternalServerError)\n}","preventionTips":["Subscribe the SNS topic to the exact webhook URL and protocol","Do not enable SNS raw message delivery for this subscription","Test with real SNS SubscriptionConfirmation envelopes captured from AWS logs/chalice-style dumps","Reject non-POST or wrong content-type requests early"],"tags":["webhook","json","unmarshal","aws","sns","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"}