セッション
セッションは、利用者 1 人の本人確認 1 回分です。進行(status)と判定(outcome.verdict)は別の値で、判定は outcome にだけ入ります。
POST /v1/verification-sessions に、次の項目を必要なものだけ入れて送ります(すべて省略できます)。
| 項目 | 内容 |
|---|---|
callbackUrl | 手続きを終えた利用者を戻す URL。管理画面の許可リストに登録した URL だけが使える |
state | 戻り先にそのまま付けて返す値。利用者ごとに推測できない値を入れ、戻ってきたときに照合する |
metadata | 自社側の相関データ(自社のユーザー ID など)。webhook と取得 API にそのまま返る。個人情報を入れない |
preRegisteredInfo | 自社が把握している本人の情報(氏名・生年月日・住所・性別)。提出された内容との照合に使われる |
callbackUrl に state を付けた URL の長さは 8000 バイトまでです(callbackUrl を省くときは state 単体で同じ上限)。metadata にも大きさの上限があり、超えると 400 が返ります。
// セッションを作り、利用者に渡す検証 URL を受け取る(Node 20 以降・サーバー側で実行する)。import { randomUUID } from "node:crypto";
const requireEnv = (name: string): string => { const value = process.env[name]; if (!value) throw new Error(`環境変数 ${name} を設定してください`); return value;};
const API_BASE = requireEnv("SUPATRUST_API_BASE");const API_KEY = requireEnv("SUPATRUST_API_KEY");
export interface CreatedSession { id: string; status: string; /** 1 回だけ使える秘密の URL。この応答でしか受け取れない */ verificationUrl: string; verificationUrlExpiresAt: string; expiresAt: string; createdAt: string;}
export async function createSession(input: { callbackUrl?: string; state?: string; metadata?: Record<string, unknown>;}): Promise<CreatedSession> { const response = await fetch(`${API_BASE}/v1/verification-sessions`, { method: "POST", headers: { authorization: `Bearer ${API_KEY}`, "content-type": "application/json" }, body: JSON.stringify(input), }); if (response.status !== 201) { // エラーの本文は { "_tag": "..." , ... }。status と _tag で分岐する(ガイド「エラー」) const body = (await response.json().catch(() => null)) as { _tag?: string } | null; throw new Error(`セッションを作成できませんでした: ${response.status} ${body?._tag ?? ""}`); } return (await response.json()) as CreatedSession;}
// 使い方const state = randomUUID(); // 自社のサーバー側セッションに保存し、戻ってきたときに照合するconst session = await createSession({ callbackUrl: "https://example.com/verification/return", state, metadata: { userId: "user_0001" }, // 自社の相関 ID。個人情報は入れない});// session.id を自社のユーザーに結びつけて保存し、利用者を session.verificationUrl へ送る。// verificationUrl はログに出さないconsole.info("session created", { sessionId: session.id, expiresAt: session.expiresAt });応答(201)の verificationUrl が、利用者に開いてもらう URL です。
- この応答でしか受け取れません(取得 API には出ません)。受け取ったらすぐ利用者に渡し、ログに出さないでください
- 1 回だけ使えます。有効期限は
verificationUrlExpiresAtです - 利用者はスマートフォンのブラウザで開きます
- セッション全体の期限は
expiresAtです。それまでに手続きが終わらないとexpiredになります
status(進行)
Section titled “status(進行)”status は次のいずれかです。
createdcollectingdecidingreviewingcompletedexpiredfailed
| status | 意味 |
|---|---|
created | 作成直後。利用者はまだ手続きを始めていない |
collecting | 利用者が手続き中(撮影・入力の途中) |
deciding | 提出がそろい、判定中 |
reviewing | 審査待ち。判定が needs_review になり、人による確認を待っている |
completed | 判定が確定した。結果は outcome.verdict |
expired | 期限までに手続きが終わらなかった。新しいセッションを作り直す |
failed | 回復できないエラーで終わった |
- 期限切れになるのは
createdとcollectingのときだけです。decidingとreviewingは期限で終わりません completed・failed・reviewingは 再処理 でcollectingに戻せます
verdict(判定)
Section titled “verdict(判定)”判定があると outcome が入ります(status が reviewing か completed のとき)。それ以外は outcome が null です。outcome.verdict は次のいずれかです。
approvedrejectedneeds_review
| verdict | 意味 |
|---|---|
approved | 本人確認できた |
rejected | 本人確認できなかった |
needs_review | 自動では決められず、人による審査に回った。審査が終わると approved か rejected の通知がもう一度届く |
outcome.source は、判定がどこで決まったかを表します。
automaticmanual_review
automaticは自動の判定、manual_reviewは人による審査です。manual_reviewのときはoutcome.reviewに審査日時とメモが入りますoutcome.reasonsは、拒否や審査に回った理由のコードの配列です。値は今後増えます。知らない値が来てもエラーにせず、無視してくださいoutcome.checksには、実施した確認ごとの要約(書類・顔・入力内容との一致)が入ります。一致したかどうかだけで、値そのものは入りません
GET /v1/verification-sessions/{id} は、状態・判定に加えて、個人情報(preRegisteredInfo ・利用者の入力 userInputInfo ・券面から読み取った項目 documentOcr)を返します。
// セッションの状態と結果を取得する(Node 20 以降・サーバー側で実行する)。// 応答には利用者の個人情報(入力内容・券面から読み取った項目)が含まれる。ログに出さない。
const requireEnv = (name: string): string => { const value = process.env[name]; if (!value) throw new Error(`環境変数 ${name} を設定してください`); return value;};
const API_BASE = requireEnv("SUPATRUST_API_BASE");const API_KEY = requireEnv("SUPATRUST_API_KEY");
/** 取得 API の応答のうち、この例で使う部分(全体は API リファレンス) */export interface SessionDetail { id: string; status: string; completedAt: string | null; purgedAt: string | null; documentOcr: { documentType: string | null; fields: { side: string; name: string; text: string | null; normalized: string | null }[]; } | null; outcome: { verdict: string; source: string; decidedAt: string; reasons: string[]; } | null;}
export async function getSession(sessionId: string): Promise<SessionDetail | null> { const response = await fetch( `${API_BASE}/v1/verification-sessions/${encodeURIComponent(sessionId)}`, { headers: { authorization: `Bearer ${API_KEY}` } }, ); if (response.status === 404) return null; // 存在しない・別のテナントのもの・ID の形式違い if (!response.ok) throw new Error(`セッションを取得できませんでした: ${response.status}`); return (await response.json()) as SessionDetail;}
// 使い方: 状態と判定で分岐するconst session = await getSession(process.argv[2] ?? "");if (session === null) { console.info("セッションが見つかりません");} else if (session.outcome === null) { // 判定がまだ無い(created / collecting / deciding)か、判定なしで終わった(expired / failed) console.info("判定なし", { status: session.status });} else { switch (session.outcome.verdict) { case "approved": // 本人確認済みとして扱う break; case "rejected": // 本人確認できなかったとして扱う break; case "needs_review": // 審査の結果を待つ。確定すると通知がもう一度届く break; } // reasons は知らない値が来ても例外にしない。券面の項目(documentOcr)は丸ごとログに出さない console.info("判定あり", { status: session.status, verdict: session.outcome.verdict, source: session.outcome.source, reasonCount: session.outcome.reasons.length, });}if (session?.purgedAt) { // 保存期間を過ぎて個人情報が消去済み。個人情報の項目は null で返る(判定結果の outcome は残る) console.info("個人情報は消去済み", { purgedAt: session.purgedAt });}- 応答の個人情報は、ログ・エラー通知・分析基盤にそのまま流さないでください
- 個人情報の取得は SupaTrust 側で記録されます
- 保存期間を過ぎると個人情報は消去されます。消去後も 404 にはならず、
purgedAtに消去日時が入り、個人情報の値がnullになります。判定結果(outcome)は残ります - 存在しない ID・別のテナントの ID・形式が違う ID は、どれも同じ 404 です