knadh/listmonk · warning
invalid signature format
Error message
invalid signature format
What it means
After scanning the comma-separated signature header, parseLettermintSignature requires both a non-zero timestamp (t=) and a non-empty v1 signature component. If either is missing, the webhook is rejected with 'invalid signature format' before any HMAC verification. This ensures a Lettermint payload cannot be processed without the complete signature material.
Source
Thrown at internal/bounce/webhooks/lettermint.go:142
)
for _, part := range strings.Split(sig, ",") {
kv := strings.SplitN(strings.TrimSpace(part), "=", 2)
if len(kv) != 2 {
continue
}
switch kv[0] {
case "t":
if _, err := fmt.Sscanf(kv[1], "%d", &ts); err != nil {
return 0, "", fmt.Errorf("invalid timestamp in signature: %v", err)
}
case "v1":
hash = kv[1]
}
}
if ts == 0 || hash == "" {
return 0, "", fmt.Errorf("invalid signature format")
}
return ts, hash, nil
}
View on GitHub (pinned to 670c01717d)
Solutions
- Ensure the Lettermint webhook integration has signature/verification enabled and the expected header is sent
- Confirm the HTTP handler reads and forwards the correct signature header to ProcessBounce
- Send a properly formed header 't=<unix-ts>,v1=<hex-hmac>' when testing manually
- Check middleware or proxies are not dropping custom headers
Example fix
// before curl -H 'X-Lettermint-Signature: v1=deadbeef' ... // after curl -H "X-Lettermint-Signature: t=$(date +%s),v1=$(computed_hmac_hex)" ...
Defensive patterns
Strategy: validation
Validate before calling
func hasLettermintSigParts(sig string) bool {
var ts int64
var hash string
for _, part := range strings.Split(sig, ",") {
kv := strings.SplitN(strings.TrimSpace(part), "=", 2)
if len(kv) != 2 { continue }
if kv[0] == "t" { fmt.Sscanf(kv[1], "%d", &ts) }
if kv[0] == "v1" { hash = kv[1] }
}
return ts != 0 && hash != ""
}
// return 400 if !hasLettermintSigParts(sigHeader) Try / catch
bounces, err := handler.ProcessBounce(sigHeader, body)
if err != nil {
if err.Error() == "invalid signature format" {
http.Error(w, "missing signature components", http.StatusBadRequest)
return
}
http.Error(w, "webhook error", http.StatusInternalServerError)
} Prevention
- Enable webhook signing in Lettermint so the t=,v1= header is always sent
- Verify the handler reads the exact header name Lettermint uses
- Ensure proxies/middleware do not strip custom signature headers
When it happens
Trigger: ProcessBounce receives a signature header missing 't=' or 'v1=' (e.g. empty header, only 't=123', only 'v1=hex', or a t value that evaluated to 0 such as 't=0' or non-numeric values that fail earlier).
Common situations: Lettermint webhook signing not enabled/misconfigured so no signature header is sent; wrong header name read in the route handler; an intermediary strips the header; manual test calls that omit the header; timestamp of literal 0 in a crafted header.
Related errors
- invalid timestamp in signature: %v
- error asn1 unmarshal of signature: %v
- invalid azure event grid shared secret
- webhook key is not configured
- invalid signature
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/82488b1442c3eeda.
Report an issue: GitHub.