knadh/listmonk · error
non 200 response on subscription URL: %v
Error message
non 200 response on subscription URL: %v
What it means
After successfully GETting the SNS SubscribeURL/UnsubscribeURL, ProcessSubscription requires HTTP 200. Any other status code returns 'non 200 response on subscription URL', meaning SNS's confirmation endpoint responded but did not accept the confirmation.
Source
Thrown at internal/bounce/webhooks/ses.go:103
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)
}
return nil
}
// ProcessBounce processes an SES bounce notification and returns a Bounce object.
func (s *SES) ProcessBounce(b []byte) (models.Bounce, error) {
var (
bounce models.Bounce
n sesNotif
)
if err := json.Unmarshal(b, &n); err != nil {
return bounce, fmt.Errorf("error unmarshalling SES notification: %v", err)
}
if err := s.verifyNotif(n); err != nil {
return bounce, err
}
View on GitHub (pinned to 670c01717d)
Solutions
- Check the logged status code: 403/410 typically means the SubscribeURL expired or token is invalid
- Re-trigger the subscription from the AWS SNS console so a fresh SubscriptionConfirmation with a new URL is sent
- Confirm the notification being processed is a live SNS message, not a replayed/stored sample
- If a proxy returns the non-200, bypass it for *.amazonaws.com
Example fix
// before: replaying an old saved SNS test payload (expired URL) -> 403 // after: request a new confirmation aws sns subscribe --topic-arn arn:aws:sns:us-east-1:1:t --protocol https --notification-endpoint https://your-host/webhook/ses # then let the fresh SubscriptionConfirmation hit the endpoint
Defensive patterns
Strategy: retry
Validate before calling
u := n.SubscribeURL // only pass SNS-hosted URLs parsed, err := url.Parse(u) valid := err == nil && strings.HasSuffix(parsed.Hostname(), ".amazonaws.com") // ensure the URL is a fresh, valid SNS confirmation URL before GET
Try / catch
err := handler.ProcessSubscription(body)
if err != nil {
var code string
if _, e := fmt.Sscanf(err.Error(), "non 200 response on subscription URL: %s", &code); e == nil {
log.Printf("SNS confirmation rejected (%s); request a fresh subscription", code)
// 403/410 => expired or replayed URL: re-subscribe via AWS CLI/console
http.Error(w, "subscription not confirmed", http.StatusBadGateway)
return
}
http.Error(w, "webhook error", http.StatusInternalServerError)
} Prevention
- Always process live SNS confirmations; never replay stored sample payloads
- Re-subscribe from the AWS console if a confirmation has aged out
- Bypass intercepting proxies for *.amazonaws.com
- Watch for duplicate confirmations and ignore already-confirmed topics
When it happens
Trigger: http.Get succeeded but the confirmation URL returned a non-200 status: expired SubscribeURL (SNS confirms are short-lived), already-confirmed subscription, replayed/old confirmation message, or SNS-side throttling/errors.
Common situations: Replaying an old stored SubscriptionConfirmation payload whose token/URL has expired; double-confirming the same subscription; network middleware (corporate proxy) intercepting and returning 403/407; SNS regional endpoint issues.
Related errors
- error requesting subscription URL: %v
- error unmarshalling SNS notification: %v
- error unmarshalling SES notification: %v
- error getting SNS cert: %v
- non-OK response from Postback server: %d
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/4fecd902a778964c.
Report an issue: GitHub.