Skip to main content

Verify a caller with KBA on a live call

The KBA caller verification concept page maps the model; this guide runs the flow as one task. By the end you can pick KBA (or deliberately not), start a challenge on a live call, submit the caller’s answers, open a verified session, and gate a sensitive action on it. The Verify console guide covers the OTP surfaces; this one covers the on-file-facts flow an agent or an IVR runs while the caller waits.

1. When to use KBA vs OTP vs biometrics

Three identity checks solve three different moments. Pick per call, not per tenant. KBA is the default first try for inbound calls: it needs nothing the caller doesn’t already have, and it draws on facts your CRM already holds. Fall back to an OTP send when the contact profile is thin, and to biometrics when the contact enrolled a voiceprint (biometrics is stronger evidence than knowledge factors and should win when available). The full OTP decision tree lives in Verify profiles and fallback chains; the biometrics lifecycle in the voice biometrics walkthrough.

2. Stateful ledger or stateless token pair — pick one

Two endpoint families drive the same verdict: Both end in the same gate: pass → a verified session with an expiry; fail → a bounded attempt budget burns down to lockout. Don’t mix the two surfaces on one call — the ledger’s attempt counter and the token pair’s per-contact budget are independent, and interleaving them makes lockout behaviour confusing to your team. The GET /voice/kba/:callId read computes the effective status at request time. A stored verified flag past its expiry reports as expired, never as verified — so a stale row from an old call can’t silently open a later gate.

3. Walk the stateful ledger end to end

Start the challenge

The response carries:
  • data.session_id — pass back on verify.
  • data.questions — the 2–4 factor/prompt pairs the agent reads aloud.
  • data.attempts / data.attempts_remaining — start at 0 and 3.
Start is idempotent while the session is pending: re-POSTing returns the same questions and counters — a double-click can’t burn the caller’s attempt budget or leak a fresh question set.

Submit the answers

Every submitted factor must match. The response carries per-factor pass/fail (data.results), the remaining attempt count, and a final status of verified, pending (more attempts allowed), or failed (locked).

Read state and handle the attempt budget

A mismatch returns 200 with data.status: "pending" and a decremented data.attempts_remaining. Up to three attempts are allowed by default; the third mismatch flips the session to terminal failed, and start then answers 409 KBA_LOCKED. To open a fresh session after lockout, pass restart: true on start — nothing else routes around the lockout. After verification, the session gates downstream actions for its TTL (four hours on the per-call ledger). Any sensitive endpoint that checks the gate — payment capture, PII disclosure, account changes — gets 403 KBA_VERIFICATION_REQUIRED until the session verifies, and again once it expires.

4. Walk the stateless token pair

Issue a challenge

Pass contact_id or phone; the response carries a data.challenge_token, the factor list, and an expiry. A challenge is single-use and short-lived (five minutes) — issue it immediately before asking, while the caller is on the line.

Verify and receive the session token

On success the response mints a data.session_token alongside data.verified: true and an assurance level. On any failure you get verified: false and a reason: the caller gave a wrong answer, the challenge expired, the challenge token was malformed, or the per-contact attempt budget tripped.

Gate a downstream action on session check

/session/check is the read-only gate downstream tools (an AI-agent tool call, a payment-capture flow, a PII lookup) call before proceeding. It never touches the database — it validates the token’s signature and expiry — so it’s cheap to call inline on every gated action.

5. Factor selection and fallbacks

A challenge draws from the facts on the linked contact record. Empty fields are simply skipped — a contact with no company on file never gets a company question. The defaults lean toward the most-common facts; the concept page lists the full per-surface factor set. Insufficient data. A contact with fewer than two verifiable fields can’t be meaningfully challenged: start answers 422 KBA_INSUFFICIENT_PROFILE_DATA with the available count in the message. The token pair answers contact_not_found the same way when the lookup itself fails. Neither error is retryable with the same inputs — the fix is to switch verification method:
  • Agent judgement: ask the caller for an account-level fact the agent can check in the CRM, then proceed with a manual note.
  • OTP send: route into your Verify profile’s delivery chain and read a code back; see Verify profiles and fallback chains.
  • Voice biometrics: if the contact enrolled a voiceprint on a prior call, verify by phrase instead; see the voice biometrics walkthrough.
Pin factor order or factor count only when you know your data quality — most tenants are better off letting empty fields skip.

6. Attempt budget, lockout, and concurrency

The attempt counter is the brute-force guard on an identity gate, so it has to be strict:
  • Ledger — three attempts by default; the session flips terminal failed on the third mismatch and only restart: true opens a new one (409 KBA_LOCKED until then).
  • Token pair — a per-contact sliding window (default five in fifteen minutes) shared by challenge issuance and verify attempts. Re-issuing challenges against the same caller doesn’t reset the budget — it spends against it.
Parallel submissions serialize against the one counter. Two near-simultaneous verify calls each see the other’s committed increment, so N parallel guesses consume N distinct attempts and trip the lockout exactly on schedule — callers can’t sneak extra tries through the request window.

7. What gates on the verified session, and how long it lasts

Sensitive actions check the gate before they proceed:
  • Payment capture — the IVR or agent tool reads a card only after KBA passes.
  • PII disclosure — tools that read contact or account details gate first.
  • Account changes — address updates, plan changes, SIM swaps all check.
The gate is read-only and short-lived by design. On the ledger, a verified session gates for four hours from verified_at, and the effective-status read computes at request time so a stored flag past its expiry reports as expired, never verified. On the token pair, the signed session token carries a 15-minute expiry inside its payload, and /session/check rejects it after that. Session expires mid-call → run the challenge again; the caller is still on the line, and restart stays cheap.

8. The audit ledger — what actually gets recorded

KBA never stores or logs an answer. Per submission, the platform records:
  • Which factor keys were checked (not the answer text).
  • Pass/fail per factor, and the resulting session status.
  • The session id, attempt count, verified/failed timestamps, and the operator id behind the action.
Every start, verify, lockout, and reset lands in the tenant audit stream and on the call record as the per-factor outcome ledger — the same audit surface the rest of the voice pillar uses. A compliance reviewer sees “email checked and passed on attempt 2 by agent Jane Doe,” never “the caller said jane@example.com.”

9. Worked example: gating a payment-capture action

An inbound caller asks to pay an invoice. The IVR (or the agent desktop) runs the challenge before it ever reads card digits:
  1. Caller reaches the pay-by-phone menu. The IVR resolves the inbound call’s linked contact, then POST /api/v1/voice/kba/{callId}/start — the response is three prompts.
  2. The IVR (or agent) asks each prompt aloud. Answers go direct to POST /api/v1/voice/kba/{callId}/verify with the returned session_id.
  3. On verified, the payment-capture tool runs its own gate (GET /api/v1/voice/kba/{callId}status: verified, inside TTL) and proceeds to DTMF card capture. On any mismatch the IVR re-reads the attempt counter from the verify response and coaches the caller through the remaining attempts; on failed it falls back to an outbound callback after a manual review.
The card digits never transit unless the gate said yes — a failed verification records per-factor outcomes and burns attempts but never reaches the payment stack. The same flow self-serve in an AI-agent handoff: the agent issues a challenge through POST /api/v1/voice/kba/challenge, then submits the final answers through /verify, then calls the payment tool with the returned session_token. The tool calls /session/check before it reads card details. See AI agent voice handback for how a flow hands a verified session between the IVR, the AI agent, and back to a human.