createAuthUrl(options)
createAuthUrl(options)Promise<{ url, nonce, sig }>Generates an HMAC-signed redirect URL and a cryptographic nonce. Async — it signs via the Web Crypto API, so it returns a Promise and must be awaited. It performs no network call.
| Option | Type | Required | Description |
|---|---|---|---|
spaceId | string | required | Your Space ID (UUID) from the Helix admin panel. |
state | string | required | Opaque base64 payload echoed back in the callback. |
origin | string | required | The browser origin your app is served from, e.g. https://app.example.com. Helix targets its completion message at exactly this origin, so it must match. Sent as sdk_origin. |
secret | string | required | Shared secret from the Helix admin panel. Used to HMAC-SHA256-sign the URL params — server-side only. |
baseUrl | string | optional | Override Helix portal URL. Defaults to https://capsule.helix.id when omitted. |
env | "sandbox" | "production" | optional | Selects which space config Helix looks up. Defaults to production — leave it unset against capsule.helix.id. |
enrollmentId | string | optional | Enrollment ID from a previous verifyCallback result. Pass it to verify this user against their existing enrollment; omit on their first session to enroll them. |
Returns Promise<{ url: string, nonce: string, sig: string }> — store nonce in the user session and hand url to the browser.
URL signing
The signature covers the canonical query string: every param except sig, sorted by raw key, then each key and value percent-encoded exactly like JavaScript's encodeURIComponent (state=a&b → state=a%26b), joined as key=value pairs with &. Other languages: !'()* stay unescaped, which Python's quote and PHP's rawurlencode escape by default. That string is HMAC-SHA256'd with secret and appended as sig (64 lowercase hex chars):
/voice-auth?space_id=…&nonce=…&state=…&env=…&sdk_origin=…&sig=<hmac-sha256-hex>Because every other param is covered, changing any of them after signing invalidates sig. Build the URL once, on the server, and pass it through untouched.
verifyAuthUrlSignature(url, secret)
verifyAuthUrlSignature(url, secret)Promise<void>Verifies the sig on an auth URL produced by createAuthUrl. Async. Rebuilds the canonical string from the URL's remaining params and compares in constant time via crypto.subtle.verify. Rejects a URL that repeats a query param. Server-side only — it needs the secret.
import { verifyAuthUrlSignature, InvalidAuthUrlSignatureError } from "@helix-id/node";
try {
await verifyAuthUrlSignature(url, process.env.HELIX_SECRET);
} catch (error) {
if (error instanceof InvalidAuthUrlSignatureError) {
// URL is unsigned or was tampered with — do not redirect the user
}
}Returns Promise<void> — resolves when the signature is valid.
Throws InvalidAuthUrlSignatureError when sig is missing or does not match, or a query param repeats.
Most integrations never call this: your own server produced the URL, so there is nothing to check. It is there for architectures where the URL crosses a trust boundary between services — a gateway, a BFF, or a queue — and the receiving side wants proof it was not rewritten in transit.
verifyCallback(query, options)
verifyCallback(query, options)Promise<VerifyCallbackResult>Verifies the HMAC-signed callback payload and returns the parsed result. Async — it uses the Web Crypto API, so it returns a Promise and must be awaited. The first argument is the raw callback query, every field a plain string.
| Option | Type | Required | Description |
|---|---|---|---|
nonce | string | required | Nonce returned by createAuthUrl, stored in the session. |
secret | string | required | Shared secret from the Helix admin panel. |
Returns Promise<{ status, statusCode, logId, state, enrollmentId?, referenceList? }> — enrollmentId is present when the space has Voice MFA or Reference List enabled, absent otherwise. referenceList is { result, matchedEnrollmentId?, matchedCategory? } on a new enrollment in a Reference List space — see Reference List results.
Throws InvalidSignatureError, NonceMismatchError, ExpiredTimestampError, MalformedCallbackError.
Reference List management
People enter your Reference List only when your server adds them. After verifying a voice-auth callback, store its enrollmentId against your user. Use that ID when your own review or customer policy calls for adding or removing the person. Lists are separate for each Space and environment.
Adding requires Reference List to be enabled and an active server-side voice template belonging to your client. An Enrollment ID alone is insufficient: a person with only a device-held template cannot be added by this endpoint and receives no_template_available. Removing an entry is allowed even when Reference List is disabled.
Both helpers run on your backend, hold the space/environment's shared secret, and perform a network request. They use https://api.helix.id by default (HELIX_DEFAULT_API_URL); createAuthUrl uses the portal at https://capsule.helix.id. Override the helpers' baseUrl for a local or alternate API deployment.
import { addToReferenceList, ReferenceListError } from "@helix-id/node";
const options = {
spaceId: process.env.HELIX_SPACE_ID,
secret: process.env.HELIX_SECRET,
enrollmentId: user.helixEnrollmentId, // stored from a verified callback
};
try {
const entry = await addToReferenceList({
...options,
category: "denied", // your label; Helix does not block this person
reason: "manual_review",
customerRef: "CASE-7",
actor: "support-team",
});
} catch (error) {
if (error instanceof ReferenceListError) {
console.error(error.status, error.code); // upstream HTTP status and error code
} else {
throw error; // transport or successful-response JSON parsing failure
}
}Remove the person in a separate server action, after your review upholds an appeal:
import { removeFromReferenceList, ReferenceListError } from "@helix-id/node";
try {
const removal = await removeFromReferenceList({
spaceId: process.env.HELIX_SPACE_ID,
secret: process.env.HELIX_SECRET,
enrollmentId: user.helixEnrollmentId,
reason: "APPEAL_UPHELD",
});
} catch (error) {
if (error instanceof ReferenceListError) console.error(error.status, error.code);
else throw error;
}addToReferenceList(options)
addToReferenceList(options)Promise<AddToReferenceListResult>| addToReferenceList option | Type | Required | Description |
|---|---|---|---|
spaceId | string | required | Space ID (UUID) whose list you manage. |
secret | string | required | Shared secret for that Space and environment. Server-side only; never transmitted. |
enrollmentId | string | required | Stored ID from a verified callback: 64 lowercase hex characters. |
env | "sandbox" | "production" | optional | Defaults to production; use the corresponding environment's secret. |
baseUrl | string | optional | API origin, default https://api.helix.id. |
actor | string | optional | Audit actor, 1–128 characters; defaults to api. |
category | string | optional | Your label matching [A-Za-z0-9_-]{1,64}. See the conventions below. |
reason | string | optional | Audit reason, 1–64 characters; free text for additions. |
customerRef | string | optional | Your case/reference, 1–256 characters; sent as customer_ref. |
| addToReferenceList result | Type | Required | Description |
|---|---|---|---|
enrollmentId | string | required | ID of the listed person. |
category | string | null | required | Stored label, or null if none was supplied. |
addedAt | string | required | Original addition timestamp in ISO 8601 format. |
listSize | number | required | Entries in this Space/environment's list. |
alreadyListed | true | optional | Present only for an existing entry (HTTP 200). |
A new entry returns HTTP 201. If the person is already listed, the endpoint returns HTTP 200 and their original timestamp/category; it does not update metadata. The enabled-feature and active-template prerequisites still apply to repeated additions.
removeFromReferenceList(options)
removeFromReferenceList(options)Promise<RemoveFromReferenceListResult>| removeFromReferenceList option | Type | Required | Description |
|---|---|---|---|
spaceId | string | required | Space ID (UUID) whose list you manage. |
secret | string | required | Shared secret for the selected Space/environment. |
enrollmentId | string | required | Listed person's ID: 64 lowercase hex characters. |
env | "sandbox" | "production" | optional | Defaults to production. |
baseUrl | string | optional | API origin, default https://api.helix.id. |
actor | string | optional | Audit actor, 1–128 characters; defaults to api. |
reason | ReferenceListRemoveReason | optional | One of the removal reasons below. |
| removeFromReferenceList result | Type | Required | Description |
|---|---|---|---|
enrollmentId | string | required | ID of the removed person. |
removedAt | string | required | Removal timestamp in ISO 8601 format. |
Removal returns HTTP 200. Removing an absent entry returns HTTP 404 not_listed, which the helper throws as ReferenceListError.
| Removal reason | Description |
|---|---|
APPEAL_UPHELD | Your review upheld the person's appeal. |
EXPIRED | Your retention or listing period ended. |
CUSTOMER_ERROR | Your organization added the person by mistake. |
OTHER | Another reason for removal. |
These reasons record your decision; they do not schedule automatic removal.
Category conventions
category is optional, client-defined text within the character/length limits above. These recommended labels are conventions, not an enum or built-in policies. Custom labels are supported, and the stored label is returned as referenceList.matchedCategory when a new enrollment matches. Your application decides how to act; Helix never blocks or grants privileges based on category.
| Recommended category | Description |
|---|---|
denied | People your policy marks for denial or review; your application enforces that policy. |
vip | People your application wants to recognize or give special handling. |
POST /api/v1/reference-list
The raw endpoint is https://api.helix.id/api/v1/reference-list. Send query parameters, no JSON body. The SDK supplies action, ts, and sig; raw clients must supply them too. Unknown query parameters are rejected.
| Reference List parameter | Type | Required | Actions | Description |
|---|---|---|---|---|
space_id | string | required | both | Space UUID. |
env | "sandbox" | "production" | required | both | Required on the wire; the SDK defaults it to production. |
ts | string | required | both | Current Unix timestamp in seconds, decimal digits. Must be within 300 seconds of server time. |
sig | string | required | both | HMAC-SHA256 signature in 64 lowercase hex characters. |
action | "add" | "remove" | required | both | Operation to perform. |
enrollment_id | string | required | both | Enrollment ID, exactly 64 lowercase hex characters. |
actor | string | optional | both | Audit actor, 1–128 characters; defaults to api. |
category | string | optional | add | Optional label matching [A-Za-z0-9_-]{1,64}. |
reason | string | optional | both | Add: 1–64 characters of free text. Remove: one of the fixed reasons above. |
customer_ref | string | optional | add | Customer case/reference, 1–256 characters. |
Sign every transmitted parameter except sig, including all optional metadata, with the selected Space/environment's shared secret. Use the same canonical query encoding as createAuthUrl: sort raw keys; percent-encode each key/value with encodeURIComponent; join key=value pairs with &; compute HMAC-SHA256 and append the lowercase hex digest as sig. Do not sign the already-serialized query string (URLSearchParams encodes spaces differently). Generate a fresh timestamp/signature for each call, keep clocks synchronized, and use HTTPS.
Endpoint HTTP statuses
These are actual HTTP statuses, separate from the voice-auth callback's statusCode. Successful outcomes below describe the operation; the JSON response contains the result fields documented above, without an outcome field.
| Reference List action | HTTP | Outcome | Description |
|---|---|---|---|
| add | 201 | added | New entry created. |
| add | 200 | already_listed | Existing entry returned with alreadyListed: true; metadata unchanged. |
| remove | 200 | removed | Entry deleted and removal journaled. |
| both | 400 | VALIDATION_ERROR | Missing/malformed or unsupported query parameters. Body: { error: { code, message } }. |
| both | 401 | unauthorized | Invalid signature, stale/future timestamp, or unavailable Space/environment/secret. |
| remove | 404 | not_listed | No entry exists in this Space/environment. |
| add | 409 | reference_list_disabled | Reference List is disabled for this Space/environment. |
| add | 409 | no_template_available | No active server-side voice template belongs to this client's Enrollment ID. |
| add | 409 | reference_list_full | The Space/environment's configured list capacity has been reached. |
Business/authentication errors use { error: "code" }; no_template_available also includes a message. Both helpers throw ReferenceListError for any non-2xx response, exposing the upstream .status and backend .code. If the failure body has no usable code (including a non-JSON response), .code is REFERENCE_LIST_REQUEST_FAILED. Transport failures and JSON parsing failures on successful responses propagate unchanged. The helpers do not retry; decide how your server handles each failure rather than forwarding upstream errors to the user automatically.