{"record":{"id":"57dc7a49ae9f26fa","repo":"knadh/listmonk","slug":"invalid-timestamp-in-signature-v","errorCode":null,"errorMessage":"invalid timestamp in signature: %v","messagePattern":"invalid timestamp in signature: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/bounce/webhooks/lettermint.go","lineNumber":134,"sourceCode":"\t}}, nil\n}\n\n// parseLettermintSignature parses a signature header of the form \"t={timestamp},v1={hex}\".\nfunc parseLettermintSignature(sig string) (int64, string, error) {\n\tvar (\n\t\tts   int64\n\t\thash string\n\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":116,"sourceCodeEnd":147,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/lettermint.go#L116-L147","documentation":"parseLettermintSignature parses the Lettermint signature header of the form 't={timestamp},v1={hex}'. When the 't=' component exists but its value cannot be parsed as an integer via fmt.Sscanf, the handler rejects the webhook with 'invalid timestamp in signature'. This guards the HMAC replay-window check that follows, which requires a numeric Unix timestamp.","triggerScenarios":"ProcessBounce is called with a signature header whose t= value is non-numeric or truncated, e.g. 't=abc,v1=...' or 't=17<..,v1=...', commonly caused by the signature header being cut off, URL-encoded, or overwritten by a proxy/reverse-proxy.","commonSituations":"A reverse proxy mangles or truncates the X-signature header; the webhook secret/header name is misconfigured so a different header value is passed in; the payload is forwarded through a gateway that re-encodes commas; testing with a hand-crafted cURL command that omits the real header format.","solutions":["Log the raw signature header and confirm it matches 't=<unix-ts>,v1=<hex>' exactly","Check that no proxy/CDN is modifying, truncating, or URL-encoding the signature header","Re-send the webhook from Lettermint (or replay the official test event) so a fresh, intact signature is generated","Verify the correct header is being read and passed to ProcessBounce in the HTTP handler wiring"],"exampleFix":"// before (client test request)\ncurl -H 'X-Lettermint-Signature: t=abc,v1=deadbeef' ...\n// after\ncurl -H \"X-Lettermint-Signature: t=$(date +%s),v1=$(computed_hmac_hex)\" ...","handlingStrategy":"validation","validationCode":"func validLettermintSigHeader(sig string) bool {\n    hasT, hasV1 := false, false\n    for _, part := range strings.Split(sig, \",\") {\n        kv := strings.SplitN(strings.TrimSpace(part), \"=\", 2)\n        if len(kv) != 2 { continue }\n        switch kv[0] {\n        case \"t\":\n            if _, err := strconv.ParseInt(kv[1], 10, 64); err == nil { hasT = true }\n        case \"v1\":\n            if kv[1] != \"\" { hasV1 = true }\n        }\n    }\n    return hasT && hasV1\n}\n// call before invoking ProcessBounce; reject with 400 if false","typeGuard":null,"tryCatchPattern":"bounces, err := handler.ProcessBounce(sigHeader, body)\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"invalid timestamp in signature\") {\n        http.Error(w, \"bad signature header\", http.StatusBadRequest)\n        return\n    }\n    http.Error(w, \"webhook error\", http.StatusInternalServerError)\n}","preventionTips":["Never hand-edit or truncate the signature header; forward it verbatim","Test the webhook pipeline with real events from Lettermint, not hand-built headers","Check reverse-proxy configs (e.g. nginx underscores_in_headers, header size limits) for header mangling"],"tags":["webhook","signature-parsing","hmac","go"],"backgroundTag":"webhook-signature-invalid","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}