{"record":{"id":"6f82a60ecc807d76","repo":"signalapp/Signal-Server","slug":"429-too-many-requests-or-409-conflict-not-allowed","errorCode":null,"errorMessage":"429 Too Many Requests or 409 Conflict (not allowed to request code)","messagePattern":"429 Too Many Requests or 409 Conflict \\(not allowed to request code\\)","errorType":"http","errorClass":"ClientErrorException","httpStatus":null,"severity":"warning","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java","lineNumber":608,"sourceCode":"          registrationFraudChecker.checkSendVerificationCodeAttempt(requestContext, storedVerificationSession,\n                  registrationServiceSession.number())\n              .updatedSession()\n              .orElse(storedVerificationSession);\n    }\n\n    if (registrationServiceSession.verified()) {\n      throw new ClientErrorException(\n          Response.status(Response.Status.CONFLICT)\n              .entity(buildResponse(registrationServiceSession, verificationSession))\n              .build());\n    }\n\n    if (!verificationSession.allowedToRequestCode()) {\n      final Response.Status status = verificationSession.requestedInformation().isEmpty()\n          ? Response.Status.TOO_MANY_REQUESTS\n          : Response.Status.CONFLICT;\n\n      throw new ClientErrorException(\n          Response.status(status)\n              .entity(buildResponse(registrationServiceSession, verificationSession))\n              .build());\n    }\n\n    final MessageTransport messageTransport = verificationCodeRequest.transport().toMessageTransport();\n\n    final ClientType clientType = switch (verificationCodeRequest.client()) {\n      case \"ios\" -> ClientType.IOS;\n      case \"android-2021-03\" -> ClientType.ANDROID_WITH_FCM;\n      default -> {\n        if (Strings.CI.startsWith(verificationCodeRequest.client(), \"android\")) {\n          yield ClientType.ANDROID_WITHOUT_FCM;\n        }\n        yield ClientType.UNKNOWN;\n      }\n    };\n","sourceCodeStart":590,"sourceCodeEnd":626,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java#L590-L626","documentation":"requestVerificationCode throws this ClientErrorException when verificationSession.allowedToRequestCode() is false. The status depends on session state: if requestedInformation is empty (no challenge info outstanding) it returns 429 TOO_MANY_REQUESTS — too many code requests; otherwise 409 CONFLICT — required information (captcha, push challenge) has not been submitted yet. The body carries the session state including any requestedInformation.","triggerScenarios":"POSTing a code request when (a) the per-session/per-number code-request rate limiter already flagged the session (429 path), or (b) requestedInformation is non-empty — e.g. captcha or push challenge still pending — so the session disallows code delivery (409 path).","commonSituations":"Rapid re-requesting of SMS/voice codes hitting attempt caps; clients skipping updateSession (captcha/push) before requesting a code; region-specific enforcement demanding captcha that the client ignored; shared IPs amplifying rate-limit hits.","solutions":["Read the response body: if requestedInformation lists CAPTCHA or PUSH_CHALLENGE, call updateSession with those proofs first, then re-request the code.","For the 429 path, wait for the indicated duration before requesting another code; respect any retryAfter hints.","Reduce code-request frequency in client retry logic (backoff, no tight loops).","Create a fresh verification session if the current one is permanently disallowed."],"exampleFix":"// before\nawait requestVerificationCode(sessionId, {transport: 'sms'});\n// after\nconst s = await getSession(sessionId);\nif (!s.allowedToRequestCode && s.requestedInformation.includes('CAPTCHA')) {\n  await updateSession(sessionId, {captcha: token});\n}\nawait requestVerificationCode(sessionId, {transport: 'sms'});","handlingStrategy":"validation","validationCode":"const s = await getSession(sessionId);\nif (!s.allowedToRequestCode) {\n  if (s.requestedInformation.length) await completeChallenges(s.requestedInformation);\n  else throw new Error('wait before requesting another code');\n}","typeGuard":"function canRequestCode(s) { return s.allowedToRequestCode === true; }","tryCatchPattern":"try { await requestVerificationCode(...); } catch (e) {\n  if (e.status === 409) await completeChallenges(e.body.session.requestedInformation);\n  else if (e.status === 429) await sleep(e.body.retryAfterMs ?? 60000);\n  else throw e;\n}","preventionTips":["Check allowedToRequestCode before requesting","Submit captcha/push proofs via updateSession first","Back off between code requests","Don't alternate transports to evade limits"],"tags":["http-429","http-409","rate-limit","verification","captcha"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}