Billionmail/BillionMail · error
failed to parse JWT: %w
Error message
failed to parse JWT: %w
What it means
Generic wrap of any error returned by jwt.Parse: malformed token, bad signature, malformed claims, or the signing-method rejection from the keyfunc. ParseUnsubscribeJWT wraps the library error with %w so errors.Is/As still work on the underlying golang-jwt cause.
Source
Thrown at core/internal/service/batch_mail/jwt.go:160
// ParseUnsubscribeJWT 解析退订JWT
func ParseUnsubscribeJWT(tokenString string) (*UnsubscribeClaims, error) {
if tokenString == "" {
return nil, errors.New("empty token string")
}
cfg := getConfig()
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(cfg.secret), nil
})
if err != nil {
return nil, fmt.Errorf("failed to parse JWT: %w", err)
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
result := &UnsubscribeClaims{}
// Extract email
if email, ok := claims["email"].(string); ok {
result.Email = email
} else {
return nil, errors.New("JWT missing email claim")
}
// Extract template ID
if templateID, ok := claims["template_id"].(float64); ok {
result.TemplateId = int(templateID)
}
// Extract task IDView on GitHub (pinned to fc36c76c05)
Solutions
- Verify the secret used to sign the token matches cfg.secret in getConfig() (same env/config source)
- Log the underlying err (errors.Unwrap) to distinguish malformed-token vs signature-mismatch vs signing-method failures
- Regenerate the unsubscribe link so a freshly signed token is issued
- Check that the full token string reaches the handler (no URL-encoding/truncation loss)
Defensive patterns
Strategy: try-catch
Validate before calling
if tokenString == "" || strings.Count(tokenString, ".") != 2 {
return fmt.Errorf("malformed unsubscribe token")
} Type guard
func looksLikeJWT(s string) bool {
parts := strings.Split(s, ".")
return len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != ""
} Try / catch
claims, err := ParseUnsubscribeJWT(tok)
if err != nil {
log.Printf("unsubscribe jwt rejected: %v", err) // includes wrapped cause
return nil, ErrInvalidUnsubscribeToken
} Prevention
- Use errors.Is/As on the wrapped cause to distinguish failure kinds
- Keep signing and verification secrets sourced from the same config object
- Rotate secrets with a grace period; old emailed links will fail otherwise
- Add integration tests covering tampered and truncated tokens
When it happens
Trigger: ParseUnsubscribeJWT receives a token string that fails jwt.Parse: corrupt/missing segments, signature computed with a different secret, expired-per-parser tokens, or the unexpected-signing-method error (240).
Common situations: Secret rotated in config but old links still in emails; token truncated by mail clients or URL handling; different environment (dev vs prod) secrets; typos when manually copying tokens.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- unexpected signing method: %v
- JWT missing email claim
- JWT has expired
- JWT missing or invalid group_id claim
- invalid token claims
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/dbed42d3d9882edf.
Report an issue: GitHub.