knadh/listmonk · error
invalid signature encoding: %v
Error message
invalid signature encoding: %v
What it means
Lettermint's signature header carries the HMAC as a hex string (v1=...). This error is thrown when that hex portion cannot be decoded — before HMAC comparison — indicating the v1 value is not valid hexadecimal.
Source
Thrown at internal/bounce/webhooks/lettermint.go:64
if len(l.hmacKey) == 0 {
return nil, fmt.Errorf("webhook key is not configured")
}
// Parse the signature header: t={timestamp},v1={hex_signature}.
ts, sigHex, err := parseLettermintSignature(sig)
if err != nil {
return nil, err
}
// Verify timestamp tolerance (300 seconds).
if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return nil, fmt.Errorf("signature timestamp expired")
}
// Decode the hex signature from the header.
sigB, err := hex.DecodeString(strings.TrimSpace(sigHex))
if err != nil {
return nil, fmt.Errorf("invalid signature encoding: %v", err)
}
// Compute HMAC-SHA256 of "{timestamp}.{body}" and compare.
mac := hmac.New(sha256.New, l.hmacKey)
mac.Write([]byte(fmt.Sprintf("%d.%s", ts, body)))
if !hmac.Equal(mac.Sum(nil), sigB) {
return nil, fmt.Errorf("invalid signature")
}
var n lettermintNotif
if err := json.Unmarshal(body, &n); err != nil {
return nil, fmt.Errorf("error unmarshalling Lettermint notification: %v", err)
}
// Map event to bounce type.
var typ string
switch n.Event {View on GitHub (pinned to 670c01717d)
Solutions
- Verify the v1 value decodes as hex: only [0-9a-f] characters and even length (64 chars for HMAC-SHA256).
- Keep the header in Lettermint's exact scheme: "t={unix_ts},v1={hex_hmac_of_timestamp.body}".
- Trim whitespace/quotes when extracting the header; check proxies aren't modifying it.
- For tests, generate the signature with hex.EncodeToString(hmac.New(sha256.New, key).Sum(nil)) rather than pasting values.
Example fix
// before: base64 digest in v1
sig := fmt.Sprintf("t=%d,v1=%s", ts, base64.StdEncoding.EncodeToString(mac.Sum(nil)))
// after: hex digest in v1
sig := fmt.Sprintf("t=%d,v1=%s", ts, hex.EncodeToString(mac.Sum(nil))) Defensive patterns
Strategy: try-catch
Validate before calling
func isHexSignatureV1(sig string) bool {
for _, part := range strings.Split(sig, ",") {
if kv := strings.SplitN(strings.TrimSpace(part), "=", 2); len(kv) == 2 && kv[0] == "v1" {
_, err := hex.DecodeString(strings.TrimSpace(kv[1]))
return err == nil
}
}
return false
} Try / catch
bounces, err := lm.ProcessBounce(sig, body)
if err != nil {
if strings.Contains(err.Error(), "invalid signature encoding") {
log.Printf("lettermint v1 signature is not hex: header=%q", sig)
http.Error(w, "bad signature format", http.StatusBadRequest)
return
}
http.Error(w, "unauthorized", http.StatusUnauthorized)
} Prevention
- Generate test signatures with hex.EncodeToString, never base64.
- Verify proxies/gateways don't truncate or rewrite the signature header.
- Reject requests with missing/short signature headers before invoking the handler.
When it happens
Trigger: Calling ProcessBounce with a sig header whose v1 value is: base64 instead of hex, empty, contains non-hex characters, has odd length, or the header was assembled with the wrong format entirely (e.g. just the raw digest without the t=...,v1=... scheme — though that usually fails earlier as invalid format).
Common situations: A gateway/proxy truncating or rewriting the signature header; manually constructed test headers with a base64 digest; copying a signature with whitespace or quotes; switching from another provider's (Stripe/Forwardemail-style) signature format to Lettermint's; header size limits cutting long signatures.
Related errors
- invalid signature encoding: %v
- invalid signature
- invalid signature
- webhook key is not configured
- signature timestamp expired
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/d4250570ed494a1a.
Report an issue: GitHub.