bitwarden/server · error · BadRequestException

Invalid Billing Sync Key

Error message

Invalid Billing Sync Key

What it means

Thrown (HTTP 400) on POST /organization-sponsorships/sync (Installation-authorized) when _validateBillingSyncKeyCommand.ValidateBillingSyncKeyAsync returns false for the sponsoring organization. The self-hosted installation must present the correct billing sync key to sync sponsorship data.

Solutions

  1. Copy the current billing sync key from the cloud organization's billing settings.
  2. Update the self-hosted installation configuration with the correct key.
  3. Verify the SponsoringOrganizationCloudId in the request is correct.
Defensive patterns

Strategy: validation

Validate before calling

// Installation client: verify the billing sync key is configured and current before syncing.
if (!model.billingSyncKey) throw new Error('Billing sync key required for sponsorship sync');
if (!model.sponsoringOrganizationCloudId) throw new Error('Sponsoring org cloud id required');

Try / catch

try {
  await post('/organization-sponsorships/sync', syncModel);
} catch (e) {
  if (e.isBadRequest && /invalid billing sync key/i.test(e.message)) {
    await refreshBillingSyncKey(); // re-fetch from cloud, retry once
  } else { throw e; }
}

Prevention

When it happens

Trigger: Self-hosted installation sends a stale or wrong billing sync key; the key was regenerated in the cloud portal; the sponsoring org id in the request does not match the configured org.

Common situations: Configuration drift between cloud billing settings and the self-hosted installation's billing sync configuration.

Related errors


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

Appendix: source

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

        await _setUpSponsorshipCommand.SetUpSponsorshipAsync(
            sponsorship,
            await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId));

        _logger.LogInformation(
            "Sponsorship redemption succeeded: SponsorshipId={SponsorshipId}, SponsoredOrganizationId={SponsoredOrganizationId}",
            sponsorship.Id,
            model.SponsoredOrganizationId);
    }

    [Authorize("Installation")]
    [HttpPost("sync")]
    public async Task<OrganizationSponsorshipSyncResponseModel> Sync([FromBody] OrganizationSponsorshipSyncRequestModel model)
    {
        var sponsoringOrg = await _organizationRepository.GetByIdAsync(model.SponsoringOrganizationCloudId);
        if (!await _validateBillingSyncKeyCommand.ValidateBillingSyncKeyAsync(sponsoringOrg, model.BillingSyncKey))
        {
            throw new BadRequestException("Invalid Billing Sync Key");
        }

        var (syncResponseData, offersToSend) = await _syncSponsorshipsCommand.SyncOrganization(sponsoringOrg, model.ToOrganizationSponsorshipSync().SponsorshipsBatch);
        await _sendSponsorshipOfferCommand.BulkSendSponsorshipOfferAsync(sponsoringOrg.DisplayName(), offersToSend);
        return new OrganizationSponsorshipSyncResponseModel(syncResponseData);
    }

    [Authorize("Application")]
    [HttpDelete("{sponsoringOrganizationId}")]
    [SelfHosted(NotSelfHostedOnly = true)]
    public async Task RevokeSponsorship(Guid sponsoringOrganizationId)
    {

        var orgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrganizationId, _currentContext.UserId ?? default);
        if (_currentContext.UserId != orgUser?.UserId)
        {
            throw new BadRequestException("Can only revoke a sponsorship you granted.");
        }

View on GitHub (pinned to e93b962371)