knadh/listmonk · error
error unmarshalling Forwardemail notification: %v
Error message
error unmarshalling Forwardemail notification: %v
What it means
After the HMAC signature is verified, Forwardemail's ProcessBounce unmarshals the request body into the forwardemailNotif struct. This error is thrown when the (signature-valid) body is not valid JSON or field types don't match the struct — e.g. `bounced_at` not an RFC3339 time or `bounce` not an object.
Source
Thrown at internal/bounce/webhooks/forwardemail.go:73
sig, err := hex.DecodeString(sigHex)
if err != nil {
return nil, fmt.Errorf("invalid signature encoding: %v", err)
}
// Generate HMAC using the request body and secret key
mac := hmac.New(sha256.New, p.hmacKey)
mac.Write(body)
expectedSignature := mac.Sum(nil)
// Compare the generated signature with the provided signature
if !hmac.Equal(expectedSignature, sig) {
return nil, errors.New("invalid signature")
}
// Parse the JSON payload
var n forwardemailNotif
if err := json.Unmarshal(body, &n); err != nil {
return nil, fmt.Errorf("error unmarshalling Forwardemail notification: %v", err)
}
// Categorize the bounce type
typ := models.BounceTypeSoft
hardBounceCategories := []string{"block", "recipient", "virus", "spam"}
for _, category := range hardBounceCategories {
if n.Bounce.Category == category {
typ = models.BounceTypeHard
break
}
}
campUUID := ""
if v, ok := n.Headers["X-Listmonk-Campaign"]; ok {
campUUID = v
}
return []models.Bounce{{View on GitHub (pinned to 670c01717d)
Solutions
- Validate the body is valid JSON (`json.Valid(body)`) before sending/expecting success, and log the Unmarshal error to find the offending field.
- Ensure `bounced_at` is an RFC3339 timestamp string and `bounce` is an object with the expected fields.
- Use Forwardemail's documented bounce webhook payload as a fixture in tests.
- Check no middleware is altering the request body between signature verification and parsing.
Example fix
// before: wrong field types
body := []byte(`{"recipient":"a@b.com","bounced_at":1725000000}`) // bounced_at as unix int
// after: RFC3339 timestamp
body := []byte(`{"recipient":"a@b.com","bounced_at":"2024-08-30T10:00:00Z"}`) Defensive patterns
Strategy: validation
Validate before calling
func isForwardemailPayload(body []byte) bool {
if !json.Valid(body) {
return false
}
var probe struct {
Recipient string `json:"recipient"`
BouncedAt string `json:"bounced_at"`
Bounce struct {
Category string `json:"category"`
} `json:"bounce"`
}
if json.Unmarshal(body, &probe) != nil {
return false
}
_, err := time.Parse(time.RFC3339, probe.BouncedAt)
return probe.Recipient != "" && err == nil
} Try / catch
bounces, err := fw.ProcessBounce(sig, body)
if err != nil {
if strings.Contains(err.Error(), "unmarshalling Forwardemail notification") {
log.Printf("forwardemail payload schema mismatch: %v; body=%s", err, body)
http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
http.Error(w, "processing failed", http.StatusInternalServerError)
} Prevention
- Keep a signed real Forwardemail bounce payload as a fixture in integration tests.
- Watch Forwardemail changelogs for webhook schema changes; validate field types like bounced_at (RFC3339).
- Validate json.Valid(body) before processing to fail fast with a clear client error.
When it happens
Trigger: Calling ProcessBounce with: an empty body, non-JSON bodies (form-encoded, plain text), JSON that doesn't match the schema (e.g. `bounced_at` as a unix number instead of an RFC3339 string, `bounce` as a string), or bodies signed but produced by a different provider version.
Common situations: Testing with a signed but hand-crafted payload that has wrong field types; Forwardemail changing their webhook schema; a middleware mutating the body after signing (re-compaction usually fine, but trimming can break JSON); posting the raw email instead of the JSON webhook envelope.
Related errors
- error unmarshalling Lettermint notification: %v
- webhook key is not configured
- invalid signature
- error reading validation data: %v
- error unmarshalling azure notification array: %v
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/1128b8db2b793cb6.
Report an issue: GitHub.