Skip to main content

KBA caller-verification session errors

Knowledge-based authentication (KBA) challenges a live caller against facts on file for the call’s linked contact. Seven error codes fire across the start → verify → gate flow, and they split into two families: session and lookup errors (a session you named does not exist or can no longer accept answers; the call or contact behind it resolved to nothing) and gate errors (the verification itself refused to start or a guarded action has no verified session). This page lets a supervisor or integrator work either family end to end. The concept behind the flow lives on the KBA caller verification concept page; the happy-path walkthrough is the KBA caller-verification guide. Read this page when either of those surfaces returned an error instead of a session.

1. The session state machine

A KBA session on the per-call ledger moves through three states:
  • pending — the start call issued the challenge; verify accepts answers and every mismatch burns one attempt.
  • verified — every submitted factor matched. The session gates sensitive actions for four hours from verified_at; after that the status read computes as expired, never as verified, and gated actions refuse again until a fresh session verifies.
  • failed — the attempt budget (three mismatches by default) ran out. Failed is terminal: only a deliberate restart: true on start opens a new challenge.
The ledger lives on the call record itself (kba_verification in the call log’s metadata), so one call carries one session at a time, and start is idempotent while the session is pending — a repeated start re-reads the same questions and counters rather than minting a new session.

2. The seven codes

Neither error family is retryable without changing the request. Decode the code first. The guard any sensitive endpoint (payment capture, PII disclosure, account change) wires to refuse an unverified caller is what fires KBA_VERIFICATION_REQUIRED; see section 5.

3. Session-not-found and session-not-active

KBA_SESSION_NOT_FOUND is almost always an integration ordering problem, not a platform fault:
  1. Your integration submitted answers to a call that never had a start, or post-surfaced a stale session_id from an earlier session on the same call. The response message names the fix: start a session first.
  2. The session expired — a verified status persists as the ledger state, but four hours past verified_at the computed status is expired, and most downstream gates stop honoring it. Open a fresh session; the caller is usually still on the line. If the integration keeps a session across long calls, refresh it at TTL boundaries rather than caching session_id forever.
  3. On the stateless token pair, the equivalent failure is a verify call with an expired or malformed challenge_token — the challenge is single-use and five-minute-lived, so submit answers while the caller is on the line, never against a token carried between calls.
KBA_SESSION_NOT_ACTIVE says the session is already settled — it reached verified or failed — and verify rejects any further answers. Read the session state (GET /api/v1/voice/kba/:callId) before submitting; a verified ledger means the gate should be passing, a failed ledger is permanent until restart. Never retry the same answers against a settled session — nothing changes.

4. Locked, insufficient-profile, and contact-lookup failures

Start rejects with one of four codes before any challenge issues. KBA_LOCKED (409) — the attempt budget ran out on a previous session, so the ledger holds terminal failed. The only deliberate way past it is POST /voice/kba/:callId/start with restart: true in the body:
Anything that accidentally routes around the lockout (re-POSTing start without restart, retrying verify) returns the same lock — the counter is the brute-force guard, so it stays strict. On the stateless pair the lockout is a per-contact sliding window (default five attempts in fifteen minutes) and re-issuing /challenge spends against it — cycling the challenge endpoint never resets the budget. KBA_INSUFFICIENT_PROFILE_DATA (422) — the linked contact has fewer than two verifiable facts on file. No retry of the same inputs passes: challenge questions come from contact data, and thin data stays thin across retries. Switch verification method:
  • Agent judgement — agent reads an account-level fact the caller can confirm in the CRM, proceeds with a manual note on the call record.
  • OTP send — run a verify profile’s delivery chain (SMS/email) and read a code back; see Verify OTP troubleshooting.
  • Voice biometrics — if the contact enrolled a voiceprint, verify by phrase instead; see voice biometrics failures.
If thin profiles are routine for a segment, fill in the contact record from your CRM sync — KBA only challenges on data you already hold. KBA_NO_CONTACT_LINKED and KBA_CONTACT_NOT_FOUND (both 422) — the call carries no contact_id, or the linked contact no longer resolves (deleted or unlinked after the call started). Both are lookup failures at start time: link a contact to the call through the voice/call stack or the CRM integration first, then start. KBA_NO_CONTACT_LINKED means “fix your call-to-contact association”; KBA_CONTACT_NOT_FOUND means “the association points to a contact that is gone.” The stateless pair collapses the second one into its contact_not_found outcome on /challenge.

5. The verification-required gate

Sensitive in-call actions refuse until a session verifies:
  • Payment capture — card read (DTMF or agent entry) is held.
  • PII disclosure — tools that read contact or account details gate first.
  • Account changes — address updates, plan changes, SIM swaps, and similar mutations.
Any guarded endpoint returns 403 KBA_VERIFICATION_REQUIRED with a message that spells out the remediation (start → verify → retry). The check is a read-only gate against the ledger’s computed status — it never mutates the session, so it is cheap to call inline. When the gate fires mid-flow:
  1. Read GET /api/v1/voice/kba/:callId — if the ledger shows verified+expired, the four-hour TTL slid past; re-run the challenge.
  2. Run start → verify while the caller is still on the line.
  3. Retry the original action once — never loop without the gate passing.

6. How the two surfaces differ

The stateful ledger and the stateless token pair carry the same verdict but address it differently, and the error meaning folds over accordingly: Don’t mix the two on one call — the ledger’s per-session counter and the token pair’s per-contact window are independent, and interleaving them makes lockout behavior impossible to explain to a reviewer.

7. Escalation bundle

A session/lookup error you can’t clear with the steps above needs this in a ticket:
  • The error.code and the full JSON envelope.
  • The meta.request_id off the failing response.
  • The call id, the session id, and the start/verify timestamps off your integration log.
  • The computed ledger state from GET /api/v1/voice/kba/:callId (attach it — it proves which of the three states you hit).
Never send support a caller’s spoken answer text. KBA records factor keys and pass/fail only — do the same in your own log scrub.