{"record":{"id":"82488b1442c3eeda","repo":"knadh/listmonk","slug":"invalid-signature-format","errorCode":null,"errorMessage":"invalid signature format","messagePattern":"invalid signature format","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/bounce/webhooks/lettermint.go","lineNumber":142,"sourceCode":"\t)\n\n\tfor _, part := range strings.Split(sig, \",\") {\n\t\tkv := strings.SplitN(strings.TrimSpace(part), \"=\", 2)\n\t\tif len(kv) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tswitch kv[0] {\n\t\tcase \"t\":\n\t\t\tif _, err := fmt.Sscanf(kv[1], \"%d\", &ts); err != nil {\n\t\t\t\treturn 0, \"\", fmt.Errorf(\"invalid timestamp in signature: %v\", err)\n\t\t\t}\n\t\tcase \"v1\":\n\t\t\thash = kv[1]\n\t\t}\n\t}\n\n\tif ts == 0 || hash == \"\" {\n\t\treturn 0, \"\", fmt.Errorf(\"invalid signature format\")\n\t}\n\n\treturn ts, hash, nil\n}\n","sourceCodeStart":124,"sourceCodeEnd":147,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/lettermint.go#L124-L147","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\ncurl -H 'X-Lettermint-Signature: v1=deadbeef' ...\n// after\ncurl -H \"X-Lettermint-Signature: t=$(date +%s),v1=$(computed_hmac_hex)\" ...","handlingStrategy":"validation","validationCode":"func hasLettermintSigParts(sig string) bool {\n    var ts int64\n    var hash string\n    for _, part := range strings.Split(sig, \",\") {\n        kv := strings.SplitN(strings.TrimSpace(part), \"=\", 2)\n        if len(kv) != 2 { continue }\n        if kv[0] == \"t\" { fmt.Sscanf(kv[1], \"%d\", &ts) }\n        if kv[0] == \"v1\" { hash = kv[1] }\n    }\n    return ts != 0 && hash != \"\"\n}\n// return 400 if !hasLettermintSigParts(sigHeader)","typeGuard":null,"tryCatchPattern":"bounces, err := handler.ProcessBounce(sigHeader, body)\nif err != nil {\n    if err.Error() == \"invalid signature format\" {\n        http.Error(w, \"missing signature components\", http.StatusBadRequest)\n        return\n    }\n    http.Error(w, \"webhook error\", http.StatusInternalServerError)\n}","preventionTips":["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"],"tags":["webhook","signature-parsing","hmac","go"],"backgroundTag":"webhook-signature-missing","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}