signalapp/Signal-Server · warning

return Response.status(429).build();

Error message

return Response.status(429).build();

What it means

The push-challenge endpoint (POST /v1/challenge/push) returns HTTP 429 when the challenge constraint check says push challenges are not permitted for this account. ChallengeConstraintChecker aggregates rate limits, CAPTCHA/registration-lock state, and abuse heuristics; pushPermitted()==false means the server has decided this account has requested too many push challenges or must satisfy another challenge first. It is a deliberate throttle, not a crash.

Solutions

  1. Stop retrying and honor the 429; wait before the next push-challenge request
  2. Check the rate-limit configuration (challenge rate limiter TTLs/limits) if legitimate traffic is throttled
  3. Complete any alternative challenge (e.g. CAPTCHA) required before requesting push challenges again
  4. Inspect ChallengeConstraintChecker wiring if constraints are unexpectedly false in a deployment

Example fix

// before: immediate retry on failure
sendPushChallenge(account);
// after: back off on 429
if (response.code() == 429) {
  long retryAfter = parseRetryAfter(response);
  scheduler.schedule(this::sendPushChallenge, retryAfter, TimeUnit.SECONDS);
}
Defensive patterns

Strategy: retry

Validate before calling

// client-side: check local request history before calling
if (lastPushChallengeAt != null && Duration.between(lastPushChallengeAt, Instant.now()).toMinutes() < COOLDOWN_MINUTES) {
  return; // skip; server will 429 otherwise
}

Try / catch

// on 429, schedule a delayed retry instead of immediate resend
if (response.code() == 429) {
  backoffScheduler.schedule(this::requestPushChallenge, cooldown(), TimeUnit.SECONDS);
}

Prevention

When it happens

Trigger: Client calls requestPushChallenge for an account whose combined constraints (per-IP, per-account rate limits, unmet CAPTCHA requirements) yield pushPermitted()=false. Repeatedly requesting push challenges for the same account or IP within the rate-limit window triggers it.

Common situations: Mobile clients auto-retrying push-challenge requests after message-send failures; load tests or shared IPs (NAT, VPN) exhausting per-IP quotas; an account stuck in a state requiring CAPTCHA but the client never solving it and re-requesting pushes.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/84ab78b3533dc991. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/ChallengeController.java:193

  @ApiResponse(responseCode = "404", description = """
      The server does not have a push notification token for the authenticated account’s main device; clients may add a push
      token and try again
      """)
  @ApiResponse(responseCode = "413", description = "Too many attempts", headers = @Header(
      name = "Retry-After",
      description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
  @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
      name = "Retry-After",
      description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
  public Response requestPushChallenge(@Auth final AuthenticatedDevice auth,
      @Context ContainerRequestContext requestContext) {

    final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
        .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));

    final ChallengeConstraints constraints = challengeConstraintChecker.challengeConstraintsHttp(requestContext, account);
    if (!constraints.pushPermitted()) {
      return Response.status(429).build();
    }
    try {
      rateLimitChallengeManager.sendPushChallenge(account);
      return Response.status(200).build();
    } catch (final NotPushRegisteredException e) {
      return Response.status(404).build();
    }
  }
}

View on GitHub (pinned to 100ab61c82)