knadh/listmonk · error
invalid signature
Error message
invalid signature
What it means
ProcessBounce computes HMAC-SHA256 over the raw body with the configured hmacKey and compares it to the hex-decoded signature using hmac.Equal. This error means the provided signature does not match the computed one, so the payload is not from Forwardemail (or was tampered with) and is rejected before JSON parsing.
Source
Thrown at internal/bounce/webhooks/forwardemail.go:67
func (p *Forwardemail) ProcessBounce(sigHex string, body []byte) ([]models.Bounce, error) {
if len(p.hmacKey) == 0 {
return nil, errors.New("webhook key is not configured")
}
// Decode the hex-encoded signature from the webhook
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
}
}
View on GitHub (pinned to 670c01717d)
Solutions
- Verify the configured hmacKey exactly matches the webhook secret in your Forwardemail account settings.
- Sign/verify over the exact raw request bytes — read the body once and hash those bytes without re-marshalling JSON.
- Ensure the signature is plain lowercase hex of the 32-byte HMAC (no prefixes, correct encoding).
- Confirm no middleware modifies the request body before ProcessBounce; log both computed and received digests when debugging.
- Check for clock-independent replay issues: hmac.Equal is constant-time, so a mismatch is a content/key problem, not timing.
Example fix
// before body, _ = json.Marshal(reparsedPayload) // mutates bytes before verify bounces, err := p.ProcessBounce(sigHex, body) // after raw, _ := io.ReadAll(c.Request().Body) // verify exact raw bytes bounces, err := p.ProcessBounce(sigHex, raw)
Defensive patterns
Strategy: validation
Validate before calling
if _, err := hex.DecodeString(sigHex); err != nil || len(sigHex) != 64 {
return echo.NewHTTPError(http.StatusBadRequest, "malformed signature")
} Type guard
func isHexSHA256(sigHex string) bool {
b, err := hex.DecodeString(sigHex)
return err == nil && len(b) == sha256.Size
} Try / catch
bounces, err := p.ProcessBounce(sigHex, rawBody)
if err != nil {
if strings.Contains(err.Error(), "invalid signature") {
// return 401; log computed vs received HMAC digest (not the key) for diagnosis
return echo.NewHTTPError(http.StatusUnauthorized)
}
return err
} Prevention
- Verify HMAC over the exact raw request bytes; never re-marshal the JSON first.
- Keep the webhook key in sync with Forwardemail when rotating.
- Read the signature header exactly as sent (plain hex, no prefixes).
- Disable any middleware that rewrites or re-encodes the request body before verification.
- Replay-test with a captured payload and its original signature in CI.
When it happens
Trigger: The sigHex supplied does not equal the HMAC-SHA256 of body under the shared key: wrong signing key, body modified before verification (e.g. re-serialized/re-indented JSON), signature not hex-encoded, or signature computed over different bytes.
Common situations: Rotated webhook keys where Forwardemail still signs with the old key; a proxy/gateway rewriting the body (compression, charset conversion, re-encoding) between receipt and verification; passing the signature with 0x prefix or base64 instead of raw hex; tests replaying a body captured with trailing newline stripped.
Related errors
- invalid signature encoding: %v
- webhook key is not configured
- invalid signature encoding: %v
- invalid signature
- invalid azure event grid shared secret
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/0ad9e68c5834ea64.
Report an issue: GitHub.