{"record":{"id":"cdf66f360ff440b0","repo":"signalapp/Signal-Server","slug":"403-forbidden","errorCode":null,"errorMessage":"403 Forbidden","messagePattern":"403 Forbidden","errorType":"http","errorClass":"ClientErrorException","httpStatus":403,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java","lineNumber":315,"sourceCode":"\n      verificationSession = verificationCheck.updatedSession().orElse(verificationSession);\n\n      verificationSession = handlePushToken(pushTokenAndType, verificationSession);\n\n      verificationSession = handlePushChallenge(updateVerificationSessionRequest, registrationServiceSession,\n          verificationSession);\n\n      verificationSession = handleCaptcha(sourceHost, updateVerificationSessionRequest, registrationServiceSession,\n          verificationSession, userAgent, verificationCheck.scoreThreshold());\n    } catch (final RateLimitExceededException e) {\n\n      final Response response = buildResponseForRateLimitExceeded(verificationSession, registrationServiceSession,\n          e.getRetryDuration());\n      throw new ClientErrorException(response);\n\n    } catch (final ForbiddenException e) {\n\n      throw new ClientErrorException(Response.status(Response.Status.FORBIDDEN)\n          .entity(buildResponse(registrationServiceSession, verificationSession))\n          .build());\n\n    } finally {\n      // Each of the handle* methods may update requestedInformation, submittedInformation, and allowedToRequestCode,\n      // and we want to be sure to store a changes, even if a later method throws\n      verificationSessionManager.update(verificationSession);\n    }\n\n    return buildResponse(registrationServiceSession, verificationSession);\n  }\n\n  /**\n   * If {@code pushTokenAndType} values are not {@code null}, sends a push challenge. If there is no existing push\n   * challenge in the session, one will be created, set on the returned session record, and\n   * {@link VerificationSession#requestedInformation()} will be updated.\n   */\n  private VerificationSession handlePushToken(","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java#L297-L333","documentation":"updateSession throws this ClientErrorException with HTTP 403 FORBIDDEN when handlePushChallenge or handleCaptcha raises ForbiddenException, meaning a required verification challenge (push challenge or captcha) was absent, invalid, or not satisfied. The response body still includes the session state so the client can see what information remains required. This is an authorization-of-flow error, not an auth-token problem.","triggerScenarios":"Updating a verification session where the push challenge token is present but invalid/expired (handlePushChallenge), or the captcha assessment did not pass and no alternative challenge was satisfied (handleCaptcha).","commonSituations":"Client skipping the captcha step; expired push challenge token delivered late; captcha solved for the wrong session/region; calling updateSession before completing prerequisite steps (requestedInformation not yet submitted).","solutions":["Inspect the 403 response body (session JSON) for requestedInformation to see which challenge is still required.","Complete the required challenge first (obtain captcha token via the captcha endpoint or deliver the push challenge) and resubmit.","Ensure push challenge tokens are submitted before their remoteExpirationSeconds window lapses.","Verify the captcha token is generated for the correct site key/session and passed in the captcha field."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const s = await getSession(sessionId);\nif (s.requestedInformation.length > 0) { completeRequiredChallenges(s.requestedInformation); }","typeGuard":null,"tryCatchPattern":"try { await updateSession(...); } catch (e) {\n  if (e.status === 403) { const session = e.body; /* inspect requestedInformation */ }\n  throw e;\n}","preventionTips":["Always read session.requestedInformation before updating","Complete captcha/push challenges before resubmitting","Don't submit challenge tokens for a different session"],"tags":["forbidden","http-403","captcha","push-challenge","verification"],"backgroundTag":"permission-denied","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"}