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.
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)
Solutions
- Copy the current billing sync key from the cloud organization's billing settings.
- Update the self-hosted installation configuration with the correct key.
- 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
- Keep the billing sync key in managed config shared between cloud and self-hosted.
- Rotate keys deliberately and update the installation atomically.
- Verify the SponsoringOrganizationCloudId matches the configured org.
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
- Invalid Billing Sync Key
- NoSeatsAvailable
- Invalid license key.
- Invalid license
- Free Bitwarden Families sponsorship has been disabled by you
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/ec99e8955fe34ca8.
Report an issue: GitHub.