{"record":{"id":"aa8374a69d1a06d7","repo":"bitwarden/server","slug":"failed-to-parse-sponsorship-token","errorCode":null,"errorMessage":"Failed to parse sponsorship token.","messagePattern":"Failed to parse sponsorship token\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs","lineNumber":176,"sourceCode":"    [HttpPost(\"redeem\")]\n    [SelfHosted(NotSelfHostedOnly = true)]\n    public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model)\n    {\n        _logger.LogInformation(\n            \"Sponsorship redemption started: SponsoredOrganizationId={SponsoredOrganizationId}, PlanSponsorshipType={PlanSponsorshipType}, TokenLength={TokenLength}\",\n            model.SponsoredOrganizationId,\n            model.PlanSponsorshipType,\n            sponsorshipToken?.Length ?? 0);\n\n        var (valid, sponsorship) = await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email);\n\n        if (!valid)\n        {\n            _logger.LogWarning(\n                \"Sponsorship redemption failed: invalid token. SponsoredOrganizationId={SponsoredOrganizationId}, SponsorshipId={SponsorshipId}\",\n                model.SponsoredOrganizationId,\n                sponsorship?.Id);\n            throw new BadRequestException(\"Failed to parse sponsorship token.\");\n        }\n\n        _logger.LogInformation(\n            \"Sponsorship token validated: SponsorshipId={SponsorshipId}, SponsoringOrganizationId={SponsoringOrganizationId}\",\n            sponsorship.Id,\n            sponsorship.SponsoringOrganizationId);\n\n        if (!await _currentContext.OrganizationOwner(model.SponsoredOrganizationId))\n        {\n            _logger.LogWarning(\n                \"Sponsorship redemption failed: user is not org owner. SponsoredOrganizationId={SponsoredOrganizationId}, SponsorshipId={SponsorshipId}\",\n                model.SponsoredOrganizationId,\n                sponsorship.Id);\n            throw new BadRequestException(\"Can only redeem sponsorship for an organization you own.\");\n        }\n\n        var freeFamiliesSponsorshipPolicy = await _policyQuery.RunAsync(\n            model.SponsoredOrganizationId, PolicyType.FreeFamiliesSponsorshipPolicy);","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs#L158-L194","documentation":"Thrown (HTTP 400) during sponsorship redemption when _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync returns valid=false. The sponsorship token is a signed/data-protected payload binding a sponsored email to a sponsorship record; validation fails if it is expired, tampered, already redeemed, or does not match the redeeming user's email.","triggerScenarios":"Token past its lifetime; token already consumed by a prior redemption; token regenerated (old token stale); the redeeming account email differs from the sponsored email claim; data-protection key mismatch across instances.","commonSituations":"Long delay between issuing and redeeming exceeds token validity; user logged in with a different email than the one that was sponsored; token copied incompletely.","solutions":["Request a fresh sponsorship token from the sponsoring organization.","Ensure the redeeming account's email exactly matches the sponsored email.","Verify data-protection keys are shared across server instances that issue and redeem tokens."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Confirm the token is present and the redeeming email matches the sponsored email before calling.\nif (!sponsorshipToken) throw new Error('Missing sponsorship token');\nif (currentUser.email.toLowerCase() !== sponsoredEmail.toLowerCase()) {\n  throw new Error('Redeeming account email must match the sponsored email');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await redeemSponsorship(model);\n} catch (e) {\n  if (e.isBadRequest && /failed to parse sponsorship token/i.test(e.message)) {\n    await requestFreshSponsorshipToken(); // then retry once\n  } else { throw e; }\n}","preventionTips":["Redeem sponsorship tokens promptly after issuance to avoid expiry.","Ensure the redeeming account email matches the sponsored email exactly.","Keep data-protection keys shared across instances that issue and redeem tokens."],"tags":["billing","sponsorship","token","validation","data-protection"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}