knadh/listmonk · error
invalid signature encoding: %v
Error message
invalid signature encoding: %v
What it means
Forwardemail's ProcessBounce expects the webhook signature as a hex-encoded HMAC-SHA256 digest. This error is thrown when the signature header value cannot be decoded from hex — before any HMAC comparison happens — meaning the header is not valid hexadecimal.
Source
Thrown at internal/bounce/webhooks/forwardemail.go:57
// Forwardemail handles webhook notifications (mainly bounce notifications).
type Forwardemail struct {
hmacKey []byte
}
func NewForwardemail(key []byte) *Forwardemail {
return &Forwardemail{hmacKey: key}
}
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)
}
View on GitHub (pinned to 670c01717d)
Solutions
- Pass only the raw hex portion of the signature header; strip any "sha256=" or similar prefix before calling ProcessBounce.
- Verify the header actually contains hex: all characters in [0-9a-fA-F] and even length.
- Check with `echo <sig> | xxd -r -p` (or equivalent) that the value decodes as hex.
- Log the header value (length + first chars) on failure to spot encoding or prefix issues; base64 signatures from another provider will fail here.
Example fix
// before: passing prefixed header value
sig := r.Header.Get("X-Signature") // "sha256=9f86d081..."
bounces, err := fw.ProcessBounce(sig, body) // invalid signature encoding
// after: strip prefix, pass pure hex
sig := strings.TrimPrefix(r.Header.Get("X-Signature"), "sha256=")
bounces, err := fw.ProcessBounce(sig, body) Defensive patterns
Strategy: try-catch
Validate before calling
func isHex(s string) bool {
_, err := hex.DecodeString(strings.TrimSpace(s))
return err == nil && len(s)%2 == 0
}
// usage before calling: if !isHex(sigHeader) { reject request } Try / catch
bounces, err := fw.ProcessBounce(sig, body)
if err != nil {
switch {
case strings.Contains(err.Error(), "invalid signature encoding"):
http.Error(w, "signature must be hex-encoded", http.StatusBadRequest)
case strings.Contains(err.Error(), "invalid signature"):
http.Error(w, "unauthorized", http.StatusUnauthorized)
default:
http.Error(w, "bad request", http.StatusBadRequest)
}
return
} Prevention
- Strip any "sha256=" style prefix from the signature header before passing it in.
- Never pass an empty header default through — reject requests missing the signature header early.
- Add a unit test covering hex decoding of the signature path.
When it happens
Trigger: Calling ProcessBounce with a sigHex value that is: base64-encoded instead of hex, contains a scheme prefix (e.g. "sha256=abcd"), has odd length or non-hex characters (whitespace, 0x prefix, uppercase g-z), or is empty/garbage from a missing header defaulting to something else.
Common situations: Forwardemail changing their signature format; a proxy or framework normalizing/transforming the header; developer passing the whole Authorization header value including a prefix; config mistake reading a base64 secret-signature instead of the hex one; HTTP header containing trailing newline not trimmed.
Related errors
- invalid signature
- invalid signature encoding: %v
- webhook key is not configured
- invalid signature
- invalid azure event grid shared secret
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/f96a49df64f789ee.
Report an issue: GitHub.