Skip to main content

Voice biometrics end to end

The voice biometrics concept page explains what a voiceprint is; this guide runs the lifecycle as one task, in operator order: enroll one caller, verify them on a live call, tune the tenant threshold, pair the verdict with your OTP chain, and erase when a deletion request arrives. The troubleshooting codes live on Voice biometrics failures — this page links there where a failure can stop you.

1. What a voiceprint proves — and where the module lives

A voiceprint proves the person: the speaker on the line matches the enrolled speaker embedding stored against one contact. An OTP proves possession: the recipient can read a message sent to a number. The two are complementary evidence — a voiceprint cannot be intercepted in transit the way a code can, and an OTP needs no prior enrollment. Voice biometrics shows up in two places in the dashboard:
  • Verify → main page — the inline voice-biometrics module below the fraud-signals panel: enrolled-voiceprint count, the recent-events ledger, the 7-day false-accept-rate sparkline, and the settings card (threshold slider plus the Auto-2FA switch). This is the day-to-day operations surface, described in the Verify console guide.
  • Contact-360 → identity card — a per-contact voice-biometrics enrollment launcher on the contact workspace, alongside the channel-send launchers. Use it when an agent has the caller on the line and enrolls just that person.
Everything both surfaces do is backed by the public Voice Biometrics API, so the same lifecycle runs from your own application — the dashboard and your integration share one ledger.

2. Enroll the first voiceprint

Enrollment is identity binding plus audio capture, and it has a consent contract: biometric data is special-category data (GDPR Article 9), so every enrollment carries the exact consent text and version the speaker agreed to. That metadata is what the erasure ledger operates on later. Audio requirements. One WAV clip, mono, up to 30 seconds and 2 MB, sent as base64 in POST /api/v1/verify/voice-biometrics/enroll. A few seconds of clean, spoken speech captures the speaker embedding; longer audio adds noise, not accuracy. The clip is analyzed before anything is stored — a played-back or synthesized recording fails the anti-spoof gate and the enrollment is refused with nothing written. Consent capture. Two body fields make the record auditable:
  • consentText — verbatim consent copy the speaker agreed to (10–5000 characters). Stored with the record.
  • consentVersion — a version tag for that copy, e.g. gdpr-art9-v1. When a consent version is retired, this tag is what identifies the enrollments it covers.
A successful enrollment returns a vp_-prefixed voiceprint id and the anti-spoof score the clip earned. Save the id — it is the handle for the erasure call later. GET /api/v1/verify/voice-biometrics/enroll/{id} reports the enrollment status of a known id; after erasure it returns 404. From the dashboard, the same step runs from the Contact-360 workspace: open the contact, use the voice-biometrics enrollment launcher on the identity card, capture the caller’s clip, and record the consent version in the dialog. The ledger row that appears matches the API record field for field. Two bounded failure classes can stop you here: a 409 DUPLICATE_VOICEPRINT (the same sample already enrolled — a retry-safe rejection, not an outage), and the per-contact hourly enrollment bound plus the tenant voiceprint quota (a 429 voiceprint_tenant_cap_exceeded when the tenant quota is full). Treat both as flow fixes, not connectivity. A 503 on the call is the sidecar layer — jump to §7.

3. Run the first verification on a live call

When an enrolled caller rings, the verify step is challenge → capture → verify:
  1. POST /api/v1/verify/voice-biometrics/challenge for the contact returns a chg_ nonce id and a phrase (for example 739215). The nonce expires after five minutes and is consumed exactly once — issue it immediately before recording, while the caller is on the line.
  2. Get the caller to read the phrase aloud and record the clip.
  3. POST /api/v1/verify/voice-biometrics/verify submits the audio with challengeNonceId and challengePhrase. A recorded voice ripped from a previous call cannot satisfy a fresh phrase — keep the challenge on for any fraud-sensitive flow.
Reading the result vocabulary. A 200 carries a decision; four values matter:
  • passed — the similarity score cleared your threshold and the clip passed anti-spoof. match: true, with the per-call confidence beside the resolved thresholds.
  • failed_no_match — a live, enrolled speaker whose similarity came in under the gate. The response carries the raw confidence against thresholds.similarity, so you can quantify how far below the verdict sat.
  • failed_spoof — the anti-spoof gate fired on the probe clip. This is a blocked attempt to investigate, not an uncertain match, and it is never stepped up by the Auto-2FA flag.
  • no_enrollment — the contact has no enrolled voiceprint. This is an enrollment failure, not a match failure: no similarity number exists for a contact with no voiceprint. Branch on it at your integration boundary and route the contact through enrollment instead of rejecting them in a second-verify loop.
Verify is keyed by a single contactId: a voiceprint enrolled under one contact can never match another contact’s attempt, by construction of the lookup. Every decision — pass, fail, spoof, or no-enrollment — lands in the recent-events ledger and the audit stream, so the console’s event table reads the same history your integration recorded.

4. Tune the tenant threshold and enable the Auto-2FA bridge

The verdict is gated by two tenant-owned settings on PATCH /api/v1/verify/voice-biometrics/settings, both also on the Verify page settings card:
  • confidence_threshold — 0.50 (most permissive) to 0.95 (most strict) on the cosine-similarity gate. Values outside the range are rejected at the API boundary.
  • auto_2fa_on_low_confidence — when enabled, a below-threshold result signals step-up to a second factor instead of a hard failure.
GET /api/v1/verify/voice-biometrics/overview round-trips both values alongside the enrolled count, recent events, and the 7-day false-accept-rate trend. Permissive versus strict. Lowering the threshold admits weaker matches — noisy lines, mobile microphones, cold callers — at the price of a wider false-accept surface. Raising it tightens the gate but pushes more legitimate callers into the fail lane. There is no platform-wide “correct” value: a banking line and a loyalty hotline legitimately sit at different points. The tradeoff is observable: the FAR sparkline is the dial that tells you whether permissive became sloppy after a downward move, and the recent-events ledger shows how far below the gate each failed_no_match sat after an upward one. Two tuning rules keep you out of trouble:
  1. Tune from evidence, not defaults. Before loosening a threshold fleet-wide, check whether the failures cluster on one device, one noisy line, or one contact whose enrollment clip was poor — re-enroll that contact rather than moving the gate for everyone.
  2. Turn on the bridge. With auto_2fa_on_low_confidence enabled, a weak-but-real verdict (0.66 against a 0.75 gate) becomes step-up intent instead of a rejected caller. The setting round-trips on GET /overview, so your integration reads it once and routes accordingly. It deliberately does not apply to failed_spoof — a spoof verdict is an investigation trigger, not a candidate for rescue.
Both settings are fully reversible: save the previous threshold back over the new one, toggle the flag off. No irreversible step, no side effects — which is why the console refreshes the whole panel on save, so you see the effect on the events below immediately.

5. Pair biometrics with a Verify fallback chain

Voice biometrics is not a channel on a verification profile — it never appears in channels[], and /verify/send has no step for it. The pairing works by keeping the two surfaces beside each other, as the Verify configuration console guide puts it: the profile’s OTP chain carries the delivery channels a voice check can fall back to, and the biometrics module decides when to fall back. The composed flow for an enrolled repeat caller:
  1. The caller rings; you challenge and verify their voice (§3).
  2. On passed, the session is verified — no OTP needed.
  3. On failed_no_match with the Auto-2FA flag set, step up into the profile’s OTP chain — send a code on the caller’s reachable channel (SMS → WhatsApp → voice) and run /verify/check, exactly as in Verify profiles and fallback chains. Device-bound step-up legs (push, TOTP, passkey) are equally valid when the end user is on a device rather than a phone line; see the factor suite guide.
  4. On no_enrollment, route to enrollment (§2), not to a rejection.
When you run the voice check in your own IVR or agent flow, feed the outcome into the composite pre-send risk score as the voice_biometrics_signal field — the composite takes the worst present channel score, so a failed biometric check cannot be washed out by an otherwise-clean aggregate. The Risk API reference has the scoring bands.

6. Operate the ledger: erasure and enrollment metadata

GET /api/v1/verify/voice-biometrics/ lists every voiceprint in your organization — ids, contacts, consent version, enrollment confidence, timestamps — never embedding bytes. The same rows are the Enrolled voiceprints table on the Verify page, cursor-paginated, with a per-row action menu. This is the ledger you operate for GDPR and deletion requests. Erasure is real. DELETE /api/v1/verify/voice-biometrics/{voiceprintId} permanently removes the voiceprint and its encrypted embedding (GDPR Article 17) — a hard delete, not a flag. From that moment:
  • The row leaves the ledger table and GET /enroll/{id} returns 404.
  • Verify for that contact resolves no_enrollment until the contact re-enrolls with fresh consent.
There is no undo on a delete; re-enrollment is the only recovery path. On the dashboard the same erasure runs from the row’s action menu — an operator answers a deletion request from the console instead of hunting a REST call. The consent version on each row is the targeting metadata: when a consent copy version is retired or disputed, list the enrollments carrying that consentVersion and erase them as a set.

7. Decode failures before you retry

Split every failure by layer before you act on it:
  • A 200 with a verdict (passed / failed_no_match / failed_spoof / no_enrollment) is a decision — branch on it per §3 and §4. Nothing to retry.
  • A deterministic 4xxINVALID_AUDIO, AUDIO_TOO_LARGE, CHALLENGE_INVALID, SPOOF_DETECTED, DUPLICATE_VOICEPRINT, voiceprint_tenant_cap_exceeded — is a request- or flow-layer rejection: fix the audio, the nonce, or the flow, not the connectivity.
  • A 5xx sidecar codeVOICE_BIOMETRICS_TIMEOUT, VOICE_BIOMETRICS_UNAVAILABLE, VOICE_BIOMETRICS_AUTH_FAILED, VOICE_BIOMETRICS_ERROR, CONFIGURATION_ERROR — means no decision was produced at all. Retry policy, fail-open vs fail-closed posture, and ticket contents for each code live on Troubleshooting: voice biometrics failures.

Where to go next