signalapp/Signal-Server · error · ForbiddenException
must not use authenticated connection for one-time donation…
Error message
must not use authenticated connection for one-time donation operations
What it means
One-time donation (boost) operations must be performed from an unauthenticated connection. createBoostPaymentIntent rejects the request with HTTP 403 if an authenticated account is present on the request.
Solutions
- Perform donation requests without Signal account authentication credentials
- Use a separate, unauthenticated HTTP client/session for donation endpoints
- Remove global auth-header interceptors for the donations API base URL
Example fix
// before
signalClient.post("/v1/donations/boost/payment_intent", request); // authed client
// after
unauthenticatedClient.post("/v1/donations/boost/payment_intent", request); Defensive patterns
Strategy: validation
Validate before calling
if (hasAuthCredentials()) throw new Error('donations must not be authenticated'); Try / catch
try { await createBoost(intent); } catch (e) { if (e.status === 403) rethrowWithHint('send donation requests without Signal auth headers'); else throw e; } Prevention
- Use a dedicated unauthenticated HTTP client for donation endpoints
- Audit global auth-header interceptors for the donations base URL
- Never reuse the authenticated messaging session for payments
When it happens
Trigger: POSTing to the boost payment-intent endpoint while presenting valid Signal account credentials.
Common situations: Clients sending donations through a client that attaches auth headers globally; misconfigured HTTP client adding the Authorization/unidentified header to donation calls.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Only primary devices may register attestations
- 403 Forbidden
- recovery password could not be verified
- must not use authenticated connection for call quality…
- Invalid action:
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/712b46f676841ef3.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/OneTimeDonationController.java:185
content = @Content(schema = @Schema(
type = "object",
properties = {
@StringToClassMapItem(key = "error", value = String.class)
})))
@ApiResponse(responseCode = "401", description = "Donation permit was invalid or already spent")
@RateLimitedByIp(RateLimiters.For.ONE_TIME_DONATION)
@ManagedAsync
public CreateBoostResponse createBoostPaymentIntent(
@Auth final Optional<AuthenticatedDevice> authenticatedAccount,
@Parameter(description = "A base64-encoded donation permit retrieved from POST /v1/donation/permit")
@HeaderParam(HeaderUtils.DONATION_PERMIT) final Optional<DonationPermitHeader> donationPermitHeader,
@NotNull @Valid final CreateBoostRequest request,
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent) throws SubscriptionInvalidAmountException {
if (authenticatedAccount.isPresent()) {
throw new ForbiddenException("must not use authenticated connection for one-time donation operations");
}
SubscriptionsUtil.recordDonationPermitPresent(donationPermitHeader.isPresent(), "boostCreate", userAgent);
final boolean spendSuccessful = donationPermitHeader.map(
permitHeader -> {
try {
return SubscriptionsUtil.verifyAndSpendDonationPermit(permitHeader.permit(), donationPermitsManager, clock);
} catch (final VerificationFailedException e) {
return false;
}
})
.orElse(false);
if (!spendSuccessful) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
validateRequestCurrencyAmount(request, BigDecimal.valueOf(request.amount), stripeManager);
final PaymentIntent paymentIntent = stripeManager.createPaymentIntent(request.currency, request.amount,View on GitHub (pinned to 100ab61c82)