コンテンツにスキップ

顔照合

顔照合は、自社で受け取った顔画像(例: 再ログイン時の自撮り・窓口で撮った写真)を、そのセッションで利用者が本人確認のときに撮ったセルフィーと比べる操作です。本人確認の判定(outcome)とは別の、追加の確認に使います。

  • そのセッションで利用者が顔の確認(セルフィー)を終えていること。終える前に呼ぶと 409(reason が liveness_not_completed)です。終えたかどうかを事前に知る項目は無いので、この 409 を「まだ」の合図として扱ってください(plan.steps に selfie が無いセッションでは常に 409 です)
  • 比べる相手は本人確認のセルフィーです。本人確認書類の顔写真とは比べません。再処理したセッションでは、利用者が撮り直すまで以前のセルフィーと比べ、撮り直した後は新しいセルフィーと比べます
  • 個人情報を消去済み(purgedAt が入っている)のセッションはセルフィーも消えているので、409(reason が purged)です

POST /v1/verification-sessions/{id}/face-match に、顔画像(JPEG か PNG)を base64(標準の文字集合 A-Z a-z 0-9 + /・末尾の = あり)にした imageBase64 を送ります。data:image/jpeg;base64,... の形でもかまいません。長さは 7000000 文字まで(元の画像で約 5MB)です。改行や空白を含んでもかまいません(76 文字で折り返す形式のまま送れます)。base64 として読めない文字列(URL-safe の - _・末尾の = が無いものを含む)は 400 です。

// 顔画像を、本人確認のときのセルフィーと照合する(Node 20 以降・サーバー側で実行する)。
// 送る画像も応答も個人情報 — ログに出さない。
import { readFile } from "node:fs/promises";
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 type FaceMatchOutcome =
| { kind: "matched"; similarity: number }
| { kind: "not_matched"; similarity: number }
| { kind: "no_face"; where: "selfie" | "image" } // どちらかに顔が見つからない
| { kind: "not_found" }
| { kind: "liveness_not_completed" } // 利用者がまだセルフィーを終えていない。終えてからやり直す
| { kind: "purged" } // 個人情報を消去済み。もう照合できない
| { kind: "invalid_image"; reason: string }; // JPEG / PNG として読めない・大きすぎる
export async function matchFace(sessionId: string, imagePath: string): Promise<FaceMatchOutcome> {
const imageBase64 = (await readFile(imagePath)).toString("base64");
const response = await fetch(
`${API_BASE}/v1/verification-sessions/${encodeURIComponent(sessionId)}/face-match`,
{
method: "POST",
headers: { authorization: `Bearer ${API_KEY}`, "content-type": "application/json" },
body: JSON.stringify({ imageBase64 }),
},
);
if (response.status === 404) return { kind: "not_found" };
if (response.status === 409) {
const { reason } = (await response.json()) as { reason: "liveness_not_completed" | "purged" };
return { kind: reason };
}
if (response.status === 400) {
const { reason } = (await response.json()) as { reason?: string };
return { kind: "invalid_image", reason: reason ?? "bad_request" };
}
if (!response.ok) throw new Error(`照合できませんでした: ${response.status}`);
const result = (await response.json()) as {
similarity: number;
isSamePerson: boolean;
status: "success" | "no_face_in_selfie" | "no_face_in_image";
};
if (result.status === "no_face_in_selfie") return { kind: "no_face", where: "selfie" };
if (result.status === "no_face_in_image") return { kind: "no_face", where: "image" };
return result.isSamePerson
? { kind: "matched", similarity: result.similarity }
: { kind: "not_matched", similarity: result.similarity };
}
// 使い方
const outcome = await matchFace(process.argv[2] ?? "", process.argv[3] ?? "");
console.info(outcome.kind);
// 同一人物かどうかの判定(isSamePerson)は、管理画面で設定する顔照合のしきい値で決まる
項目内容
similarity顔の類似度(0-1)。status が success 以外のときは 0
isSamePerson同一人物と判定したか。similarity がテナントの顔照合しきい値(管理画面の設定)以上なら true。本人確認の顔照合と同じしきい値です
statussuccess = 両方の顔を比べられた / no_face_in_image = 顔が見つからず比べられなかった(ほとんどは送った画像に顔が無い。別の画像で送り直す)/ no_face_in_selfie = セルフィー側の顔を確認できなかった(まれ)

status が success 以外のときは similarity が 0・isSamePerson が false です。「一致しなかった」と「比べられなかった」を分けて扱ってください。

  • 400 FaceMatchImageInvalid: 送った画像が使えない。reason が invalid_image_format(JPEG / PNG として読めない — base64 の中身を確かめる)か image_too_large(縮小して送る)。本文の形が合わないときも 400
  • 404: セッションが無い(別のテナントのもの・ID の形式違いも同じ)
  • 409 FaceMatchNotAllowed: reason が liveness_not_completed(利用者の手続きを待ってやり直す)か purged(もうできない)
  • 5xx: 照合の基盤が一時的に応答しない。時間をおいて再試行する
  • 送った画像は SupaTrust に保存されません。照合のためにその場で使うだけです
  • 照合したことは SupaTrust 側で記録されます(操作履歴)
  • 送る画像も応答も個人情報です。画像をログに出さない・応答をそのまま分析基盤に流さないでください
  • 顔照合は本人確認の結果(outcome)を変えません。webhook も届きません