> ## 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.

# KBA caller verification: knowledge-based challenges on a live call

> How knowledge-based authentication (KBA) verifies a caller's identity against the on-file contact profile — challenge factors, the bounded attempt budget, verified sessions, and the audit ledger stored on the call record.

# KBA caller verification

An inbound call reaches your contact center with a phone number — nothing more. Before an agent discusses account details, takes a payment, or changes anything on the account, someone has to answer: *is this caller who the number's contact profile says they are?* Knowledge-based authentication (KBA) answers it by challenging the caller with questions drawn from facts already on file for the call's linked contact — email, the last 4 digits of the phone number, the full name, the company name.

Orbit ships KBA as a voice-pillar primitive, alongside the OTP-code model documented in [Verification session lifecycle](/concepts/verification-lifecycle) and enrolled-voiceprint matching in [Voice biometrics](/concepts/voice-biometrics). This page is the concept-level map: what a challenge is, which endpoints drive it, how the attempt budget behaves, and where the audit ledger lives.

## Why KBA exists

OTP verification proves someone controls a phone or email you can reach *out-of-band* — it dispatches a code on a second channel. Voice biometrics prove the caller sounds like an enrolled voiceprint. Neither fits the classic contact-center moment: the caller is already on the line, the agent needs a check *now*, and voiceprint enrollment or an SMS-capable number isn't assumed.

KBA fills that gap with evidence your CRM already holds. The agent reads two or three prompts aloud — "Can you confirm the email we have on file?", "Can you confirm the last 4 digits of the phone number?", "Can you confirm the full name on this account?", "Can you confirm the company name?" — and submits the caller's spoken answers. No new PII is collected: the expected answers come from the existing contact record linked to the call, and answers are never logged or audited verbatim, only per-factor pass/fail outcomes.

## The endpoint surface under `/voice/kba`

Two resource shapes sit under the same prefix. Pick the one that matches your agent workflow — both end in the same verified/not-verified gate.

**Stateful per-call ledger.** Useful when you want every start, attempt, and lockout readable on the call record itself:

| Method | Path                               | Purpose                                                 |
| ------ | ---------------------------------- | ------------------------------------------------------- |
| `POST` | `/api/v1/voice/kba/:callId/start`  | Begin (or idempotently re-read) a challenge for a call. |
| `POST` | `/api/v1/voice/kba/:callId/verify` | Submit the caller's answers for the session.            |
| `GET`  | `/api/v1/voice/kba/:callId`        | Read the current session state for the call.            |

**Stateless signed-token pair.** Useful when you'd rather round-trip a challenge token than address a call id:

| Method | Path                              | Purpose                                                                                                                             |
| ------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `POST` | `/api/v1/voice/kba/challenge`     | Issue knowledge factors for a contact (body: `contact_id` or `phone`). Returns a `challenge_token`, the factor list, and an expiry. |
| `POST` | `/api/v1/voice/kba/verify`        | Submit answers against the token. Returns `verified`, an assurance level, and on success a `session_token`.                         |
| `POST` | `/api/v1/voice/kba/session/check` | Validate a previously issued `session_token` (the read-only gate sensitive actions call before proceeding).                         |

## Factor selection and question count

A challenge draws 2–3 questions from the on-file profile — a default of three where the data supports it. Factors that are empty on the contact record are skipped, and a contact with fewer than two verifiable fields can't be meaningfully challenged at all: start returns a `422 KBA_INSUFFICIENT_PROFILE_DATA`, and the call needs an alternate verification method (agent judgement, a callback, an OTP send). The stateless pair defaults to date-of-birth and ZIP factors when your tenant configuration doesn't pin a factor list.

## The bounded attempt budget and lockout

Every mismatched submission burns one attempt against a bounded budget — three on the stateful ledger before the session flips failed; a configurable windowed count on the stateless pair (default five per contact). This counter is the brute-force guard on an identity gate, so submissions serialize correctly under concurrency: parallel guesses each consume their own attempt and trip the lockout on schedule rather than sharing a counter value.

Exhaustion is terminal. On the stateful ledger the session sits in `failed` until you deliberately pass `restart: true` to start a fresh challenge — a stray retry can't route around the lockout. On the stateless pair, repeated challenge issuance counts against the same per-contact budget, so cycling `/challenge` doesn't reset it either.

## Verified sessions, and what gates on them

A fully-matched submission opens a verified session. Reads compute the effective status at request time — a stored `verified` flag past its expiry reports as expired, never as verified — so stale rows can't silently gate later actions. In the stateless pair the verify response hands back a `session_token`, and `/session/check` is the read-only gate payment, PII-disclosure, and account-change tools call before they proceed.

## The migration-free ledger

The stateful session lives on the call record — in the call log's metadata under `kba_verification` — rather than in a new table. This is Orbit's extend-metadata pattern (the same one lightweight annotations like MCID use): a read-model cache on the existing call row, so the feature ships with no schema migration and everything stays inside your tenant schema. The cache-cell shape holds the session id, status, factor list, attempt counters, and the verified/failed timestamps.

## Answers stay out of the record

Only outcomes are persisted or audited: which factor keys were checked, and the pass/fail result for each. Spoken answer values are never written to logs or the audit trail — KBA is a tenant-owned control over *your* call data, not a capture of new caller PII.

## Outbound signalling is never involved

KBA is read-and-compare over data already on file plus annotation of the call record. It originates no outbound call or message and touches no carrier leg, so all outbound traffic continues to exit only through the Devotel softswitch — the verification flow itself never wires a signaling path of its own.

## Challenge → verify with curl

The stateful ledger, end to end:

```bash theme={null}
# 1. Start a challenge on the live call
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/kba/call_abc123/start" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

The response carries the `session_id` and the question set (`factor` + `prompt` pairs — the caller-facing text the agent reads aloud).

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

A `verified` status comes back when every submitted factor matches; a mismatch returns per-factor results and the remaining attempt count, and the session locks when the budget runs out.

```bash theme={null}
# 3. Read the gate
curl "https://api.orbit.devotel.io/api/v1/voice/kba/call_abc123" \
  -H "Authorization: Bearer $ORBIT_API_KEY"
```

Sensitive actions (payments, PII disclosure, account changes) can require this gate before they accept the call — expect a `403 KBA_VERIFICATION_REQUIRED` until the session verifies.

## Where the how-to lives

* [Verification session lifecycle](/concepts/verification-lifecycle) — the OTP-code model for out-of-band proof of possession.
* [Voice biometrics](/concepts/voice-biometrics) — enrolled-voiceprint matching for callers with a voiceprint on file.
* [Voice call lifecycle](/concepts/voice-call-lifecycle) — where the call record KBA annotates comes from.
