bitwarden/server · error · BadRequestException

Failed to parse sponsorship token.

Error message

Failed to parse sponsorship token.

What it means

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.

Source

Thrown at src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs:176

    [HttpPost("redeem")]
    [SelfHosted(NotSelfHostedOnly = true)]
    public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model)
    {
        _logger.LogInformation(
            "Sponsorship redemption started: SponsoredOrganizationId={SponsoredOrganizationId}, PlanSponsorshipType={PlanSponsorshipType}, TokenLength={TokenLength}",
            model.SponsoredOrganizationId,
            model.PlanSponsorshipType,
            sponsorshipToken?.Length ?? 0);

        var (valid, sponsorship) = await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email);

        if (!valid)
        {
            _logger.LogWarning(
                "Sponsorship redemption failed: invalid token. SponsoredOrganizationId={SponsoredOrganizationId}, SponsorshipId={SponsorshipId}",
                model.SponsoredOrganizationId,
                sponsorship?.Id);
            throw new BadRequestException("Failed to parse sponsorship token.");
        }

        _logger.LogInformation(
            "Sponsorship token validated: SponsorshipId={SponsorshipId}, SponsoringOrganizationId={SponsoringOrganizationId}",
            sponsorship.Id,
            sponsorship.SponsoringOrganizationId);

        if (!await _currentContext.OrganizationOwner(model.SponsoredOrganizationId))
        {
            _logger.LogWarning(
                "Sponsorship redemption failed: user is not org owner. SponsoredOrganizationId={SponsoredOrganizationId}, SponsorshipId={SponsorshipId}",
                model.SponsoredOrganizationId,
                sponsorship.Id);
            throw new BadRequestException("Can only redeem sponsorship for an organization you own.");
        }

        var freeFamiliesSponsorshipPolicy = await _policyQuery.RunAsync(
            model.SponsoredOrganizationId, PolicyType.FreeFamiliesSponsorshipPolicy);

View on GitHub (pinned to e93b962371)

Solutions

  1. Request a fresh sponsorship token from the sponsoring organization.
  2. Ensure the redeeming account's email exactly matches the sponsored email.
  3. Verify data-protection keys are shared across server instances that issue and redeem tokens.
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the token is present and the redeeming email matches the sponsored email before calling.
if (!sponsorshipToken) throw new Error('Missing sponsorship token');
if (currentUser.email.toLowerCase() !== sponsoredEmail.toLowerCase()) {
  throw new Error('Redeeming account email must match the sponsored email');
}

Try / catch

try {
  await redeemSponsorship(model);
} catch (e) {
  if (e.isBadRequest && /failed to parse sponsorship token/i.test(e.message)) {
    await requestFreshSponsorshipToken(); // then retry once
  } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/aa8374a69d1a06d7. Report an issue: GitHub.