{"record":{"id":"f96a49df64f789ee","repo":"knadh/listmonk","slug":"invalid-signature-encoding-v","errorCode":null,"errorMessage":"invalid signature encoding: %v","messagePattern":"invalid signature encoding: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bounce/webhooks/forwardemail.go","lineNumber":57,"sourceCode":"\n// Forwardemail handles webhook notifications (mainly bounce notifications).\ntype Forwardemail struct {\n\thmacKey []byte\n}\n\nfunc NewForwardemail(key []byte) *Forwardemail {\n\treturn &Forwardemail{hmacKey: key}\n}\n\nfunc (p *Forwardemail) ProcessBounce(sigHex string, body []byte) ([]models.Bounce, error) {\n\tif len(p.hmacKey) == 0 {\n\t\treturn nil, errors.New(\"webhook key is not configured\")\n\t}\n\n\t// Decode the hex-encoded signature from the webhook\n\tsig, err := hex.DecodeString(sigHex)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid signature encoding: %v\", err)\n\t}\n\n\t// Generate HMAC using the request body and secret key\n\tmac := hmac.New(sha256.New, p.hmacKey)\n\tmac.Write(body)\n\texpectedSignature := mac.Sum(nil)\n\n\t// Compare the generated signature with the provided signature\n\tif !hmac.Equal(expectedSignature, sig) {\n\t\treturn nil, errors.New(\"invalid signature\")\n\t}\n\n\t// Parse the JSON payload\n\tvar n forwardemailNotif\n\tif err := json.Unmarshal(body, &n); err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling Forwardemail notification: %v\", err)\n\t}\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/forwardemail.go#L39-L75","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: passing prefixed header value\nsig := r.Header.Get(\"X-Signature\") // \"sha256=9f86d081...\"\nbounces, err := fw.ProcessBounce(sig, body) // invalid signature encoding\n\n// after: strip prefix, pass pure hex\nsig := strings.TrimPrefix(r.Header.Get(\"X-Signature\"), \"sha256=\")\nbounces, err := fw.ProcessBounce(sig, body)","handlingStrategy":"try-catch","validationCode":"func isHex(s string) bool {\n    _, err := hex.DecodeString(strings.TrimSpace(s))\n    return err == nil && len(s)%2 == 0\n}\n// usage before calling: if !isHex(sigHeader) { reject request }","typeGuard":null,"tryCatchPattern":"bounces, err := fw.ProcessBounce(sig, body)\nif err != nil {\n    switch {\n    case strings.Contains(err.Error(), \"invalid signature encoding\"):\n        http.Error(w, \"signature must be hex-encoded\", http.StatusBadRequest)\n    case strings.Contains(err.Error(), \"invalid signature\"):\n        http.Error(w, \"unauthorized\", http.StatusUnauthorized)\n    default:\n        http.Error(w, \"bad request\", http.StatusBadRequest)\n    }\n    return\n}","preventionTips":["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."],"tags":["webhook","hmac","hex","signature","forwardemail"],"backgroundTag":"webhook-signature-verification-failed","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}