{"record":{"id":"8d9793c2458a2630","repo":"knadh/listmonk","slug":"error-getting-sns-cert-v","errorCode":null,"errorMessage":"error getting SNS cert: %v","messagePattern":"error getting SNS cert: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bounce/webhooks/ses.go","lineNumber":205,"sourceCode":"\t}\n\n\tb.WriteString(\"Timestamp\" + \"\\n\" + n.Timestamp + \"\\n\")\n\n\tif n.Token != \"\" {\n\t\tb.WriteString(\"Token\" + \"\\n\" + n.Token + \"\\n\")\n\t}\n\tb.WriteString(\"TopicArn\" + \"\\n\" + n.TopicArn + \"\\n\")\n\tb.WriteString(\"Type\" + \"\\n\" + n.Type + \"\\n\")\n\n\treturn b.Bytes()\n}\n\n// verifyNotif verifies the signature on a notification payload.\nfunc (s *SES) verifyNotif(n sesNotif) error {\n\t// Get the message signing certificate.\n\tcert, err := s.getCert(n.SigningCertURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting SNS cert: %v\", err)\n\t}\n\n\tsign, err := base64.StdEncoding.DecodeString(n.Signature)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn cert.CheckSignature(x509.SHA1WithRSA, s.buildSignature(n), sign)\n}\n\n// getCert takes the SNS certificate URL and fetches it and caches it for the first time,\n// and returns the cached cert for subsequent calls.\nfunc (s *SES) getCert(certURL string) (*x509.Certificate, error) {\n\t// Ensure that the cert URL is Amazon's.\n\tu, err := url.Parse(certURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/ses.go#L187-L223","documentation":"verifyNotif wraps any error from getCert when retrieving the SNS signing certificate referenced by the notification's SigningCertURL. getCert can fail for URL validation, HTTP fetch errors, non-200 responses, or PEM/x509 parse errors; all surface as this wrapped message. Without the certificate, the library cannot verify the notification's signature.","triggerScenarios":"Called from ProcessSubscription or ProcessBounce when the notification's SigningCertURL is malformed or not an Amazon URL (invalid SNS certificate URL), the HTTPS fetch to sns.amazonaws.com fails (network/DNS outage), Amazon returns non-200, or the body is not a valid PEM certificate.","commonSituations":"Corporate egress firewall blocking outbound HTTPS to sns.<region>.amazonaws.com; forged/spoofed webhook posts with attacker-controlled SigningCertURL (rejected here); Amazon rotating/cert URL 404ing transiently; empty SigningCertURL in hand-crafted test payloads.","solutions":["Unwrap the error to see the root cause (%v prints the inner getCert error, e.g. 'invalid SNS certificate URL' vs a network error).","Verify outbound network access to the certificate host (curl the SigningCertURL from the server).","Confirm the SES/SNS notification includes a valid SigningCertURL pointing at sns.<region>.amazonaws.com.","If transient, SNS will redeliver the notification — ensure your endpoint returns 5xx on this error so SNS retries instead of swallowing it.","Check the cert cache is not serving stale entries and that getCert's HTTP fetch and PEM parsing handle your region's cert format."],"exampleFix":"// before: opaque wrap loses context for ops\nreturn fmt.Errorf(\"error getting SNS cert: %v\", err)\n// after: return the error unwrapped and classify for retry\nif err := ...; err != nil {\n  return fmt.Errorf(\"error getting SNS cert: %w\", err) // use %w so errors.Is/As work\n}","handlingStrategy":"retry","validationCode":"u, err := url.Parse(notif.SigningCertURL)\nif err != nil || u.Host == \"\" {\n  return errors.New(\"notification missing SigningCertURL\")\n}\nresp, err := http.Head(notif.SigningCertURL)\nif err != nil || resp.StatusCode != http.StatusOK {\n  return fmt.Errorf(\"cert URL unreachable: %s\", notif.SigningCertURL)\n}","typeGuard":"func hasValidCertURL(n sesNotif) bool {\n  u, err := url.Parse(n.SigningCertURL)\n  return err == nil && u.Scheme == \"https\" && strings.HasSuffix(u.Host, \".amazonaws.com\")\n}","tryCatchPattern":"if err := ses.ProcessBounce(notif); err != nil {\n  if strings.Contains(err.Error(), \"error getting SNS cert\") {\n    // transient network/cert fetch issue: return 5xx so SNS redelivers\n    log.Error(\"cert fetch failed, will let SNS retry\", \"err\", err)\n    http.Error(w, \"temporary failure\", http.StatusServiceUnavailable)\n    return\n  }\n  http.Error(w, \"bad request\", http.StatusBadRequest)\n}","preventionTips":["Verify outbound HTTPS access to sns.<region>.amazonaws.com from the webhook host","Monitor egress failures — this error is often the first sign of network/DNS trouble","Return 5xx (not 2xx) on this error so SNS retries the delivery","Keep the in-memory cert cache warm; pre-fetch region certs at startup if possible"],"tags":["go","webhook","sns","certificate","signature-verification"],"backgroundTag":"certificate-fetch-failed","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}