Troubleshooting: voice biometrics failures
The enroll and verify calls on/api/v1/verify/voice-biometrics hand each
audio sample to the inference sidecar, and every failure this page covers is
one of the ways that hand-off can break. The result vocabulary on a 200
(passed, failed_no_match, failed_spoof, no_enrollment) is the
decision layer — covered on
Voice biometrics, not here. This page owns the
layer underneath it: the calls that never got a decision because the sidecar
did not answer, rejected the signature, or answered with something unusable.
The handful of codes below are deliberately few. Read error.code off the
envelope and you know which side owns the fix — connectivity codes are
yours to retry, the auth code means a secret drift to resolve, and the
config code is an operator-side fix on our deployment.
Code matrix — symptom to fix
Codes that share an endpoint but are not sidecar failures —
INVALID_AUDIO
(undecodable base64, or a rejected sample), AUDIO_TOO_LARGE (over the
30-second / 2 MB sample cap), CHALLENGE_INVALID (a half-passed, expired,
or mismatched liveness nonce), SPOOF_DETECTED, DEEPFAKE_DETECTED, and
DUPLICATE_VOICEPRINT — are deterministic request- or biometric-layer
verdicts: fix the audio or the flow, not the connectivity.
Sidecar connectivity
Both codes here are 503s, and both mean the audio never reached a working analyzer. The distinction matters for what you tell us:VOICE_BIOMETRICS_TIMEOUT— the sidecar connection opened but no response arrived within 10 seconds. Normal inference completes well under a second, so a timeout points at an overloaded or wedged sidecar, not a slow link.VOICE_BIOMETRICS_UNAVAILABLE— the request never got as far as a response: connection refused, DNS, or a reset. Enroll and verify raise this against the sidecar;POST /challengeraises the same code against the challenge store when it cannot persist a nonce.
Cause table
Retry-safety
Both 503 codes are safe to retry. Enroll and verify do not write anything before the sidecar answers — a retry after a connectivity 503 re-runs the same attempt end to end, it cannot double-enroll a voiceprint (a genuine duplicate sample is rejected withDUPLICATE_VOICEPRINT 409, a different
envelope entirely). Use exponential backoff rather than a tight loop: three
retries spaced over tens of seconds covers a rolling restart; a
millisecond-interval loop spends your rate limit on the same outage. On
POST /challenge, prefer re-issuing a challenge after a failure over
reusing one — a consumed or partial nonce is the one state a retry cannot
repair.
Envelope sample
Ticket checklist
If retries over a bounded window keep returning the same 503, open a ticket with:- The code verbatim (
VOICE_BIOMETRICS_TIMEOUTorVOICE_BIOMETRICS_UNAVAILABLE) and the endpoint you called. - The request IDs off
meta.request_idfor two or three of the failing calls, with their timestamps — that brackets the outage window. - Your organization ID (Settings → Organization, or
organizationIdfromGET /api/v1/me). - Whether enroll and verify both fail, or only one — the split separates a sidecar-wide condition from a single-path one.
Sidecar auth
VOICE_BIOMETRICS_AUTH_FAILED (500) fires on one thing only: the sidecar
rejected the internal HMAC signature on the request. The public half of the
call already authenticated — your API key was accepted, the route ran, and
the failure happened on the internal hop to the analyzer. Your integration
cannot cause this, and nothing you change in the request retires it.
The signature is computed per request from a shared secret pair held on the
API and the sidecar. This error means the pair drifted — almost always a
secret rotation that landed on one side and not the other, or a stale
sidecar still signing against the previous value.
Secret rotation checklist
- Do not retry in a loop. Every retry signs with the same drifted secret and fails identically — a retry storm adds load without changing the outcome.
- Check the timestamp spread. A healthy-to-failing cutover at one instant marks the rotation moment; interleaved successes and failures mark a half-rolled deployment.
- Escalate immediately. Pair realignment is operator-side. Include the first failing request ID and the last succeeding one — that narrows the rotation window to minutes.
Envelope sample
Ticket checklist
- The code (
VOICE_BIOMETRICS_AUTH_FAILED) and the endpoint. - The first failing request ID and the last succeeding one — the rotation window lives between them.
- Your organization ID.
- Whether every call fails or traffic is mixed — mixed failure means two sidecar generations disagree and naming that pattern shortens the fix.
Unexpected sidecar error
VOICE_BIOMETRICS_ERROR (503) is the fallthrough: the sidecar answered,
but its answer was unusable — an HTTP status outside the mapped set, a 200
with a non-JSON body, or a 200 body that fails validation (a missing or
malformed embedding, spoof_score, or model_versions field). The
validation gate exists deliberately: a partial answer must never slip past
the anti-spoof check as a silent pass, so a malformed body fails loudly as
a 503 instead of degrading into a weak verdict.
Cause table
Log capture
Keep the full error message — it carries the offending status or the exact field list the validator rejected (embedding, spoof_score, and so on),
and that string is the entire diagnostic. Two or three entries with their
request IDs and timestamps are enough; there is no tenant-side packet
capture or sidecar log you can pull yourself.
Envelope sample
Ticket checklist
- The code (
VOICE_BIOMETRICS_ERROR) and the endpoint. - The full message string, verbatim — the status number or field list inside it is the diagnosis.
- Request IDs and timestamps for the failing calls.
- Your organization ID.
- How long the failures persisted across your retries.
Configuration
CONFIGURATION_ERROR (500) with the DEVOTEL_VOICE_BIOMETRICS_INTERNAL_SECRET
wording fires before any sidecar call is attempted: the deployment is
missing the internal signing secret, so no request can be signed at all.
Every enroll and every verify fails on it, from the moment the
configuration drifted, for every tenant on the deployment.
This is operator-side — the fix is to restore the secret on the platform
configuration, and there is nothing tenant-side to tune, rotate, or
re-send. When you see it, treat it as an incident rather than a runbook
step: capture one envelope and escalate immediately. It never resolves
through your retry policy, and it is the one code on this page where a
green dashboard panel alongside red API results is expected — the panel
reads stored settings; the calls fail at signing before verification could
run.
Fallback semantics — what a failure does to your flow
A 503 or 500 from this page’s codes returns no decision at all — no similarity score, no spoof verdict, nothing to branch a caller journey on. Decide the outage posture at your integration boundary, before it happens:- Fail-closed: hold or route to an agent. Treat a retry-exhausted 503
the way you treat
result: "failed_no_match"— the caller does not get the verified path. This fits high-assurance flows (payment release, account recovery) where an unverified speaker must not pass. Queue the caller for a manual check or an agent rather than rejecting outright. - Fail-open into step-up. When the verify outcome is weak or
unavailable, fall through to a second factor — the
auto_2fa_on_low_confidenceflag on Verify profiles and fallback chains is the in-product version of this pattern (it signals step-up on a weak verdict; the same branch point can consume a 503 instead of a weak verdict). An OTP channel is the usual second rung. - Do not fail-open into acceptance. A connectivity failure is not a match — an integration that maps “verify errored” to “caller verified” converts every sidecar outage into a bypass. The one exception is deliberate and logged: a documented operator choice for a specific low-risk flow, never the default.
What NOT to try
- Do not retry
VOICE_BIOMETRICS_AUTH_FAILEDorCONFIGURATION_ERRORin a loop. Both fail identically on every attempt — the drift is on the signing side, not in your request. - Do not treat a 503 as a match failure. A
failed_no_matchverdict means the sidecar ran and the speaker scored below your threshold; a 503 means the sidecar never scored anything. Branching a 503 into your “reject” path is correct only if fail-closed is your chosen posture — and even then, log it as an outage, not as a rejection. - Do not switch off the liveness challenge to “get calls through”.
If
POST /challengereturnsVOICE_BIOMETRICS_UNAVAILABLE, sending verify without challenge fields is a supported degradation — the route says so in its own message — but it drops the replay-resistance gate. Restore the challenge flow when the store recovers. - Do not send longer samples to beat a timeout. The 10-second budget is fixed; longer audio only spends more of it on inference. Trim to the spoken phrase.
See also
- Voice biometrics — the decision layer on a
200: thresholds, the
no_enrollmentoutcome, and the result vocabulary this page deliberately does not cover. - Voice biometrics console — the dashboard surfaces: enrollment wizard, recent events, threshold settings.
- Voice Biometrics API reference — endpoint shapes and request examples for every call this page fails on.
- Troubleshooting hub — the runbook index; the retry-safety table there covers the generic 503 posture this page narrows for sidecar failures.
- Error codes reference — the platform-wide catalog these codes live in.