Skip to main content

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 /challenge raises 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 with DUPLICATE_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:
  1. The code verbatim (VOICE_BIOMETRICS_TIMEOUT or VOICE_BIOMETRICS_UNAVAILABLE) and the endpoint you called.
  2. The request IDs off meta.request_id for two or three of the failing calls, with their timestamps — that brackets the outage window.
  3. Your organization ID (Settings → Organization, or organizationId from GET /api/v1/me).
  4. 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

  1. The code (VOICE_BIOMETRICS_AUTH_FAILED) and the endpoint.
  2. The first failing request ID and the last succeeding one — the rotation window lives between them.
  3. Your organization ID.
  4. 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

  1. The code (VOICE_BIOMETRICS_ERROR) and the endpoint.
  2. The full message string, verbatim — the status number or field list inside it is the diagnosis.
  3. Request IDs and timestamps for the failing calls.
  4. Your organization ID.
  5. 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_confidence flag 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.
Whichever posture you pick, log the posture you took. When we pull a failure window apart, “you accepted N callers during the outage” versus “you held N callers” is the first question the timeline has to answer.

What NOT to try

  • Do not retry VOICE_BIOMETRICS_AUTH_FAILED or CONFIGURATION_ERROR in 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_match verdict 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 /challenge returns VOICE_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_enrollment outcome, 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.