> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify a caller with KBA on a live call: challenge, answers, session gate

> Run knowledge-based caller verification end to end — pick a factor mix, walk the per-call ledger with curl, gate a payment or PII action on the verified session, and read the audit trail.

# Verify a caller with KBA on a live call

The [KBA caller verification concept page](/concepts/caller-verification-kba) 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](/guides/verify-console) 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.

| Factor set       | Proves                                             | Needs on the caller                                        | Needs on file                           | Fits when                                                          |
| ---------------- | -------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------ |
| KBA              | Knowledge of on-file facts                         | Speech only (already on the line)                          | ≥2 verifiable contact fields            | Caller already talking; no enrollment, no reachable second channel |
| OTP              | Possession of a reachable channel                  | An SMS/WhatsApp/email-capable endpoint the caller can read | A verified, reachable address or number | You can dispatch a code and wait for a read-back                   |
| Voice biometrics | The speaker's voice matches an enrolled voiceprint | Clean speech for a phrase                                  | A consent-captured enrollment clip      | Repeat callers with prior enrollment; highest assurance            |

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](/guides/verify-fallback-chains); the biometrics lifecycle in the [voice biometrics walkthrough](/guides/voice-biometrics-enrollment-walkthrough).

## 2. Stateful ledger or stateless token pair — pick one

Two endpoint families drive the same verdict:

| Path            | Shape                                                                                         | Address by                   | Session state                       | Choose when                                                                                               |
| --------------- | --------------------------------------------------------------------------------------------- | ---------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Per-call ledger | `POST /voice/kba/:callId/start` → `POST /voice/kba/:callId/verify` → `GET /voice/kba/:callId` | Call id                      | Stored on the call record itself    | An agent works the call in the desktop; you want the attempt counter and the lockout readable on the call |
| Token pair      | `POST /voice/kba/challenge` → `POST /voice/kba/verify` → `POST /voice/kba/session/check`      | Signed tokens you round-trip | No stored session — tokens carry it | An IVR or AI agent self-serve flow asks the questions; you hold tokens between turns                      |

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

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/kba/call_8f2a1c9d/start" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

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

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/kba/call_8f2a1c9d/verify" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "kba_call_8f2a1c9d_9f2e1a4c3b8d",
    "answers": [
      { "factor": "full_name", "answer": "Jane Doe" },
      { "factor": "email", "answer": "jane@example.com" },
      { "factor": "phone_last4", "answer": "4291" }
    ]
  }'
```

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

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/kba/call_8f2a1c9d" \
  -H "Authorization: Bearer $ORBIT_API_KEY"
```

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

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/kba/challenge" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contact_id": "contact_8f2a1c9d" }'
```

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

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/kba/verify" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "challenge_token": "eyJ...",
    "answers": { "dob": "1988-06-09", "zip": "10001" }
  }'
```

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

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/kba/session/check" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "session_token": "eyJ..." }'
```

`/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](/concepts/caller-verification-kba) 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](/guides/verify-fallback-chains).
* Voice biometrics: if the contact enrolled a voiceprint on a prior call, verify by phrase instead; see the [voice biometrics walkthrough](/guides/voice-biometrics-enrollment-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](mailto: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](/voice/ai-agent-handback) for how a flow hands a verified session between the IVR, the AI agent, and back to a human.

## Cross-links

* [KBA caller verification concept](/concepts/caller-verification-kba) — the model behind this walkthrough.
* [Verify profiles and fallback chains](/guides/verify-fallback-chains) — the OTP chain KBA sits ahead of.
* [Voice biometrics enrollment walkthrough](/guides/voice-biometrics-enrollment-walkthrough) — the enrolled-voiceprint alternative when a contact has a voiceprint on file.
* [AI agent voice handback](/voice/ai-agent-handback) — handing a verified session between an IVR and an AI agent.
* [Verification session lifecycle](/concepts/verification-lifecycle) — the OTP-code model opposite the KBA concept.
