Troubleshoot Partner 2FA
Enrollment, challenges, one-shot verification tokens, disable behavior, and Partner API failures.
On this page
Partner 2FA failures are usually enrollment state, challenge lifecycle, or one-shot token handling.
Enrollment stays pending
Symptom
pollEnrollment / waitForEnrollment never reaches an enrolled terminal state.
Most likely causes
- User abandoned the enrollment QR/link
- Polling a different enrollmentId than enable2FA returned
- Enrollment expired
How to diagnose
- Inspect pollEnrollment status and terminal flags
- Confirm the user opened the enrollment URL
- Check for KeyraEnrollmentExpiredError / expired status
- Did enable2FA return an enrollmentId?
- Are you polling that same id?
- Did the user open the enrollment URL?
- Is status expired/cancelled/failed?
- Start a new enable2FA if terminal failure
How to fix
- Start a new enable2FA.
- Keep enrollmentId stable in UI state until terminal.
Related errors: expired, KeyraTimeoutError, already_enrolled
Related SDK: enable2FA(), waitForEnrollment(), pollEnrollment()
Related API: POST /v1/identities/enroll, GET /v1/identities/enroll/{enrollmentId}/status
Related guides: Enrollment
User is reported as not enrolled
Symptom
get2FAStatus shows not enrolled, or createChallenge returns not_enrolled.
Most likely causes
- Never enrolled
- Disabled/revoked
- Different externalUserId or project
How to diagnose
- Compare project + externalUserId across enroll and challenge.
- Check for prior disable2FA.
How to fix
- Complete enrollment.
- Re-enroll after disable.
Related errors: not_enrolled
Related SDK: get2FAStatus(), enable2FA(), startAuthentication()
Related API: POST /v1/identities/status, POST /v1/auth/challenge
Related guides: Enrollment
Challenge stays pending
Symptom
Challenge remains PENDING until timeout/expiry.
Most likely causes
- User never opened/scanned challengeUrl
- Wrong URL displayed
- Challenge already expired
- Polling a different challengeId
How to diagnose
- Poll status for expired
- Verify challengeId continuity
- Confirm device completed hosted verification
- Is the challenge expired?
- Did the user open/scan the challenge URL?
- Did the device complete verification?
- Are you polling the ORIGINAL challenge ID?
- Inspect status / errors
How to fix
- Re-display QR quickly.
- Start a new challenge if expired.
- Use waitForChallengeApproval.
Related errors: expired, KeyraTimeoutError, not_enrolled
Related SDK: startAuthentication(), waitForChallengeApproval(), pollChallenge()
Related API: POST /v1/auth/challenge, GET /v1/auth/challenge/{challengeId}
Related guides: Authentication
Verification token is missing / disappeared
Symptom
User approved, but verificationToken is undefined on later polls.
Most likely causes
- verificationToken is one-shot on poll when status becomes approved
- Subsequent polls after first approved response omit the token
- UI polled again after capturing once and discarded the first payload
How to diagnose
- Confirm status === approved on the poll that returned the token
- Check whether a later poll ran and overwrote state with a token-less body
- Was status ever approved?
- Did any poll response include verificationToken?
- Did a later poll run after that?
- Use waitForChallengeApproval and consume once
How to fix
- Use waitForChallengeApproval() which waits until approved && verificationToken present
- Store the token immediately, then call consumeChallenge once
Related errors: already_consumed, invalid_token
Related SDK: waitForChallengeApproval(), pollChallenge(), consumeChallenge()
Related API: GET /v1/auth/challenge/{challengeId}, POST /v1/auth/challenge/{challengeId}/consume
Related guides: Verification, waitForChallengeApproval()
User approved but application does not continue
Symptom
KEYRA challenge is approved, but login UX never finishes.
Most likely causes
- Backend never called consumeChallenge
- Lost one-shot verificationToken
- Started a new challenge on “complete” instead of consuming
- App session not created after consume
How to diagnose
- Log challengeId continuity.
- Confirm consume runs exactly once.
- Confirm Set-Cookie / session after consume.
How to fix
- Do not create a new challenge after approval.
- Use waitForChallengeApproval + consumeChallenge.
Related errors: already_consumed, invalid_token
Related SDK: waitForChallengeApproval(), consumeChallenge()
Related API: GET /v1/auth/challenge/{challengeId}, POST /v1/auth/challenge/{challengeId}/consume
Related guides: Verification
Disable 2FA succeeded but an existing challenge still works
Symptom
After disable2FA, an in-flight challenge can still be completed.
Most likely causes
- Current behavior: disable does not cancel already in-flight challenges
- App still accepting consume for a challenge started before disable
How to diagnose
- Confirm challenge was created before disable.
- Check whether your app still polls/consumes it.
How to fix
- Stop starting new challenges after disable
- In your app, reject incomplete challenges for revoked users if required by policy
Related errors: not_enrolled, not_found
Related SDK: disable2FA()
Related API: POST /v1/identities/{identityId}/disable, POST /v1/auth/challenge/{challengeId}/consume
Related guides: Disable 2FA
