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

  1. Ensure the Lettermint webhook integration has signature/verification enabled and the expected header is sent
  2. Confirm the HTTP handler reads and forwards the correct signature header to ProcessBounce
  3. Send a properly formed header 't=<unix-ts>,v1=<hex-hmac>' when testing manually
  4. 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

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


AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01). Data as JSON: /api/errors/82488b1442c3eeda. Report an issue: GitHub.