@helix-id/voice-auth
v0.1.2
Published
Backend SDK for Helix Voice Authentication redirect flow
Readme
@helix-id/voice-auth
Backend SDK for Helix Voice Authentication — a redirect-based voice identity verification flow.
Your app redirects users to Helix, where they speak a short numeric challenge. Helix verifies their voice and redirects back with an HMAC-signed result. This package handles nonce generation, URL building, and callback verification.
Installation
npm install @helix-id/voice-authQuick Start
1. Generate the Redirect URL (backend)
import { createAuthUrl } from "@helix-id/voice-auth";
app.get("/auth/start", (req, res) => {
const state = Buffer.from(JSON.stringify({ action: "approve_tx", txId: "abc123", returnTo: "/dashboard" })).toString(
"base64",
);
const { url, nonce } = createAuthUrl({
spaceId: process.env.HELIX_SPACE_ID,
state,
});
req.session.helixNonce = nonce;
res.json({ url });
});2. Redirect the User (frontend)
const { url } = await fetch("/auth/start").then((r) => r.json());
window.location.href = url;3. Verify the Callback (backend)
import { verifyCallback, InvalidSignatureError, NonceMismatchError, ExpiredTimestampError } from "@helix-id/voice-auth";
import type { CallbackQuery } from "@helix-id/voice-auth";
app.get("/auth/callback", (req, res) => {
try {
// verifyCallback expects every field to be a plain string.
// Frameworks like Express can parse duplicate query params as arrays
// (e.g. ?status=verified&status=failed → { status: ["verified","failed"] }).
// Normalize req.query before passing it in:
const query: CallbackQuery = Object.fromEntries(
Object.entries(req.query).map(([k, v]) => [k, Array.isArray(v) ? v[0] : String(v)]),
) as CallbackQuery;
const result = verifyCallback(query, {
nonce: req.session.helixNonce,
secret: process.env.HELIX_SECRET,
});
req.session.helixNonce = null;
const state = JSON.parse(Buffer.from(result.state, "base64").toString());
if (result.status === "verified") {
res.redirect(state.returnTo);
} else {
res.redirect(`${state.returnTo}?error=${result.statusCode}`);
}
} catch (error) {
if (error instanceof InvalidSignatureError) {
res.status(401).json({ error: "Invalid signature" });
} else if (error instanceof NonceMismatchError) {
res.status(401).json({ error: "Nonce mismatch" });
} else if (error instanceof ExpiredTimestampError) {
res.status(401).json({ error: "Callback expired" });
} else {
throw error;
}
}
});API
createAuthUrl(options)
Generates a redirect URL and cryptographic nonce.
| Option | Type | Required | Description |
| --------- | -------- | -------- | -------------------------------------------------------- |
| spaceId | string | Yes | Your Space ID (UUID) |
| state | string | Yes | Opaque state echoed back in callback |
| baseUrl | string | No | Override Helix URL (default: https://capsule.helix.id) |
Returns { url: string, nonce: string }. Store nonce in the user's session.
verifyCallback(query, options)
Verifies the HMAC-signed callback and parses the result.
| Option | Type | Required | Description |
| -------- | -------- | -------- | --------------------------------------------- |
| nonce | string | Yes | Nonce from createAuthUrl, stored in session |
| secret | string | Yes | Shared secret from Helix admin panel |
Returns { status, statusCode, logId, state }.
Throws: InvalidSignatureError, NonceMismatchError, ExpiredTimestampError, MalformedCallbackError.
Important:
querymust be a flatRecord<string, string>. Some frameworks (Express, Koa) can parse duplicate query parameters as arrays. Normalize the query object before passing it toverifyCallback— see the callback example above.
Status Codes
| status_code | status | Description |
| -------------------------- | ---------- | ------------------------------------------ |
| voice_verified | verified | Voice matched successfully |
| voice_enrolled | verified | First-time user — voice profile created |
| challenge_mismatch | failed | Spoken words didn't match displayed digits |
| synthetic_voice_detected | failed | Audio flagged as AI-generated or spoofed |
| voice_mismatch | failed | Voice didn't match enrolled profile |
Testing
Use the test credentials for local development:
import { createAuthUrl } from "@helix-id/voice-auth";
import { HELIX_TEST_SPACE_ID, HELIX_TEST_SECRET } from "@helix-id/voice-auth/testing";
const { url, nonce } = createAuthUrl({
spaceId: HELIX_TEST_SPACE_ID,
state: "test",
});| Credential | Value |
| ------------ | --------------------------------------------- |
| Space ID | a1b2c3d4-e5f6-7890-abcd-ef1234567890 |
| Secret | tk_test_hx_4a7f2c9d8e1b3f6a5c2d9e8f7b4a1c3d |
| Callback URL | http://localhost:8080/helix/callback |
License
MIT
