Verification sessions
One walk of a program’s flow, opened by a key and read back by id
Get started with verification sessions
A verification session is one person or one business walking a program’s flow on Hira’s hosted page. A customer’s backend opens it with a key that reaches Identity verification, hands its link to the person once, and reads the outcome back by the session’s id, vsn_ and 32 hexadecimal characters. A session opens on a hosted, active program of the key’s account and names the party by the account’s own customerRef. Today sessions are created in the sandbox account: a live key answers 403 engine_not_connected.
Lifecycle
| status | Means |
|---|---|
| created | Opened; no step has landed. |
| in_progress | At least one step has landed. |
| completed | Every step concluded; the session carries a decision. It reopens to in_progress when an officer asks the person for more, or when a retry decline is reached before the deadline. |
| expired | Past expiresAt without a step. A new link reopens it as created. |
| abandoned | Past expiresAt after a step. A new link reopens it as in_progress. |
| canceled | Canceled through the API or the console. Final. |
| error | Stopped on an error; errorDetail.code says why. Final, and announced by no webhook. |
Past expiresAt, a sweep that runs every minute stamps the session expired or abandoned; until then a read shows created or in_progress. seq is the session’s change counter: 0 at create and raised by each change of the session, a move to error included; an announced change carries the new value, which a webhook repeats. Gaps between the values an endpoint receives are normal. assurance reads measured when a source checked the person and simulated when the outcome was asked for in the sandbox.
Decisions
A completed session’s decision is the program’s answer; it is null while the session is open.
| decision | Approvals status | check() verdict | Meaning |
|---|---|---|---|
| in_review | pending | Hold | A person on the account owes an answer. |
| approved | approved | Allow | Proceed. |
| declined | declined | Deny | Do not proceed. |
The read adds decisionDetail: declineType (retry or final, whether asking again can help), decidedBy (officer, policy or system), decidedAt, decisionId, reason (step_outcome, party_details_differ, unjudged_step, too_many_cases), the walk’s caseIds, and reasons, one per step that concluded failed or unable-to-verify with its causes. When an analyst resolves the walk’s case, the customer risk rating they state, low, medium or high, reads as riskRating; a dismissed or accepted case’s rating is not answered.
Origins
origin says how the walk began: api (a key’s create), link (a program’s shared link) or operator (an operator started it in the console). The list answers every session of the account, whatever its origin.
Applicant link
The session’s url is the applicant link: the hosted page, /verify# and a token. It is a bearer link, so whoever holds it walks the session, and it goes to the person it is for and nobody else. Only a hash of the token is stored, so the url comes back on a fresh create and on POST …/links alone; get and list never carry it.
A link lasts 72 hours unless expiresIn says otherwise, from 600 seconds (10 minutes) to 604800 (7 days); anything else answers 400 expires_in_out_of_range, “A link lasts between 10 minutes and 7 days.” createLink(id) mints a new link and moves the deadline, and the earlier link stops opening. It refuses a completed, canceled, errored or redacted session and one opened through a program’s shared link with 409 session_state_invalid.
Idempotency and replay
A create carries an Idempotency-Key header, 1 to 255 printable ASCII characters; the SDK sends one per call and reuses it on its own retries. The key is kept for 24 hours under the account and the key’s source app, and stored only once the create has passed every rule.
- Same key, same body:
201with the session the first create made,Idempotent-Replayed: true, and nourl. The SDK answersreplayed: true;createLink(id)mints a link. - Same key, other body:
422idempotency_key_reused, “This Idempotency-Key was already used with a different request body. Send the same body, or a new key.” - Two creates racing on one key make one session.
Cancel is safe to retry: a canceled session answers unchanged. A retried createLink whose first answer was lost mints a second link to the same session. A 409 after a retried simulate means the first send landed.
Simulate in the sandbox
simulate(id, { outcome }) concludes every remaining step of an open session with verified, failed or unable-to-verify, through the same recorder the hosted page posts to, so each step is evaluated and settled by the program’s policies as a walk’s is. Every simulated step carries assurance simulated. A face step with no granted consent concludes unable-to-verify whatever was asked. A create may instead preset the outcome with simulate: { outcome }, and the hosted page then opens each step on it.
A live account answers 400 simulate_not_allowed; a session not created or in_progress, or past its deadline, answers 409 session_state_invalid. A sandbox account creates up to 500 sessions per UTC day.
In the console
Sessions opened through the API sit beside console and link runs on the Verifications page. The register, the run page and its cards name the opener as “API key” and the key’s name. An API run offers Cancel alone; its link card reads “Created through the session API, which handed the link to the account’s own system. The session API reissues it, and the earlier link then stops opening.” A simulated result reads “Simulated”, and the party’s record counts it beside its verified figure, never in it.
Example
import { Hira, HiraError } from "@hira/sdk"; const hira = new Hira({ apiKey: process.env.HIRA_API_KEY, endpoint: process.env.HIRA_ENDPOINT }); const session = await hira.verificationSessions.create({ programId: process.env.HIRA_PROGRAM_ID, customerRef: "user-1042", expiresIn: 3600, }); console.log(session.status, session.url); // "created" "https://…/verify#…" await hira.verificationSessions.simulate(session.id, { outcome: "failed" }); const s = await hira.verificationSessions.get(session.id); console.log(s.status, s.decision, s.assurance); // "completed", the program's decision, "simulated"; // a retry decline before the deadline reads "in_progress" instead console.log(s.decisionDetail?.reasons); // each failed step with its causes try { await hira.verificationSessions.cancel(session.id); } catch (err) { if (!(err instanceof HiraError)) throw err; console.log(err.status, err.code); // 409 "session_state_invalid" when the session is completed }
The example app wires the same calls into a server, and the API reference lists every field and refusal.