knadh/listmonk · error
error unmarshalling SNS notification: %v
Error message
error unmarshalling SNS notification: %v
What it means
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.
Source
Thrown at internal/bounce/webhooks/ses.go:85
// requests and bounce notifications.
type SES struct {
mu sync.RWMutex
certs map[string]*x509.Certificate
}
// NewSES returns a new SES instance.
func NewSES() *SES {
return &SES{
certs: make(map[string]*x509.Certificate),
}
}
// ProcessSubscription processes an SNS topic subscribe / unsubscribe notification
// by parsing and verifying the payload and calling the subscribe / unsubscribe URL.
func (s *SES) ProcessSubscription(b []byte) error {
var n sesNotif
if err := json.Unmarshal(b, &n); err != nil {
return fmt.Errorf("error unmarshalling SNS notification: %v", err)
}
if err := s.verifyNotif(n); err != nil {
return err
}
// Make an HTTP request to the sub/unsub URL.
u := n.SubscribeURL
if n.Type == "UnsubscriptionConfirmation" {
u = n.UnsubscribeURL
}
resp, err := http.Get(u)
if err != nil {
return fmt.Errorf("error requesting subscription URL: %v", err)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("non 200 response on subscription URL: %v", resp.StatusCode)View on GitHub (pinned to 670c01717d)
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
Example fix
// before
curl -X POST .../ses -d 'not json'
// after
curl -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&..."}' Defensive patterns
Strategy: validation
Validate before calling
func validSNSEnvelope(b []byte) bool {
var probe struct {
Type string `json:"Type"`
MessageId string `json:"MessageId"`
TopicArn string `json:"TopicArn"`
Message string `json:"Message"`
Signature string `json:"Signature"`
SigningCertURL string `json:"SigningCertURL"`
Timestamp string `json:"Timestamp"`
}
return json.Unmarshal(b, &probe) == nil && probe.Type != "" && probe.SigningCertURL != ""
}
// return 400 if !validSNSEnvelope(body) Try / catch
err := handler.ProcessSubscription(body)
if err != nil {
if strings.HasPrefix(err.Error(), "error unmarshalling SNS notification") {
log.Printf("SNS raw body: %s", string(body))
http.Error(w, "invalid SNS payload", http.StatusBadRequest)
return
}
http.Error(w, "webhook error", http.StatusInternalServerError)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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 SES notification: %v
- error unmarshalling postmark notification: %v
- error unmarshalling Sendgrid notification: %v
- error requesting subscription URL: %v
- non 200 response on subscription URL: %v
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/ca2ece1cb335835d.
Report an issue: GitHub.