옴니ID 개발자 문서
가맹점 콘솔
문서 목록

가맹점 바이오 API

직접 구현최종 수정 2026년 7월 31일

가맹점이 구현하고 omni가 호출하는 API. 등록 데이터 저장·조회와 Ed25519 서명 검증.

목차

가맹점이 구현하고 omni API 서버가 호출하는 표준 Bio API 스펙입니다.

이 API는 가맹점별 개별 adapter를 기본으로 하지 않고, omni가 제공하는 표준 계약을 가맹점이 구현하는 방식입니다.

Base URL#

가맹점별로 omni merchant 설정에 등록합니다.

https://merchant.example.com/omni/bio/v1

Authentication#

omni API 서버는 가맹점 바이오 API 호출 시 Ed25519 request signature를 사용합니다.

omni가 전역 서명 개인키로 요청에 서명하고, 가맹점은 omni가 공개한 공개키로 서명을 검증해서 요청이 진짜 omni API 서버에서 온 것인지 확인합니다. 가맹점은 OAuth token issuer를 구현하지 않으며, 검증용 비밀(secret)을 발급받거나 보관할 필요도 없습니다 — 공개키는 비밀이 아닙니다.

Headers#

X-Omni-Merchant-Id: merchant_123
X-Omni-Key-Id: omni-sign-2026-07
X-Omni-Timestamp: 2026-07-02T13:00:00Z
X-Omni-Nonce: 550e8400-e29b-41d4-a716-446655440000
X-Omni-Content-SHA256: hex_sha256_raw_body
X-Omni-Signature: v2=base64_ed25519_signature
X-Request-Id: req_...
Idempotency-Key: enr_01J...   # write request only
Content-Type: application/json
  • X-Omni-Key-Idomni 전역 서명키 id입니다 (가맹점별 값이 아님). 공개키 목록에서 이 id로 검증 키를 선택합니다.
  • signature header의 version prefix는 v2=입니다. 다른 prefix는 거부합니다.

Canonical Request#

서명 대상 문자열은 다음 순서와 newline을 고정합니다.

OMNI-ED25519
{http_method}
{path_and_query}
{timestamp}
{nonce}
{content_sha256}

Example:

OMNI-ED25519
POST
/omni/bio/v1/enrollments
2026-07-02T13:00:00Z
550e8400-e29b-41d4-a716-446655440000
8d969eef6ecad3c29a3a629280e686cff8fab8d8a39d7d...

Public Key Distribution#

omni 서명 공개키는 무인증 공개 엔드포인트로 게시합니다.

GET {omni_api_base}/v1/signing-keys
{
  "keys": [
    {
      "key_id": "omni-sign-2026-07",
      "algorithm": "ed25519",
      "public_key_base64": "MCowBQYDK2Vw...",
      "status": "active"
    }
  ]
}
  • public_key_base64SPKI DER를 base64 인코딩한 값입니다 (대부분의 언어 표준 라이브러리가 바로 읽는 형식).
  • statusactive(현재 서명에 사용) 또는 retiring(rotation 중 병행 게시되는 구 키)입니다.
  • 가맹점은 요청의 X-Omni-Key-Id로 키를 선택합니다. key_id 기준으로 캐시하고, 모르는 key_id가 오면 목록을 재조회한 뒤 그래도 없으면 거부합니다.
  • 공개키는 연동 시 안전 채널로 전달받을 필요가 없습니다 — 이 엔드포인트와 연동 문서가 배포 채널입니다.

Key Rotation#

서명키 교체는 공개키 병행 게시로 무중단 진행합니다.

  1. omni가 새 키를 active, 기존 키를 retiring으로 /v1/signing-keys에 병행 게시하고 새 키로 서명을 시작합니다.
  2. 가맹점은 key_id 기준 캐시 덕분에 추가 작업이 없습니다 (모르는 key_id 수신 시 목록 재조회만 구현되어 있으면 됨).
  3. 유예 기간(가맹점 공지 후 최소 7일) 뒤 retiring 키를 목록에서 제거합니다.

Verification Rules#

가맹점 바이오 API는 다음 순서로 검증합니다.

  1. 필수 header 존재 여부를 확인합니다.
  2. X-Omni-Key-Id로 omni 서명 공개키를 찾습니다 (/v1/signing-keys 캐시).
  3. X-Omni-Merchant-Id가 가맹점 자신에게 발급된 merchant_id와 일치하는지 확인합니다.
  4. timestamp 허용 오차를 확인합니다. MVP 기본값은 5분입니다.
  5. nonce가 재사용되지 않았는지 확인합니다.
  6. raw request body bytes로 X-Omni-Content-SHA256을 다시 계산합니다.
  7. canonical request를 구성합니다.
  8. X-Omni-Signaturev2= 뒤 base64 값을 Ed25519 서명 검증합니다.
  9. 검증 성공 후에만 저장/조회 요청을 처리합니다.

Security Notes#

  • features, enc_key는 가맹점 DB에 저장되는 바이오 등록 데이터입니다.
  • 가맹점은 Bio API request/response body를 access log, application log, APM, tracing, error report에 남기기 금지
  • signature mismatch 로그에도 request body 원문 남기기 금지
  • nonce 저장소 장애 시 fail open 금지
  • 서명 검증에 필요한 것은 공개키뿐이므로 가맹점 측에 유출 시 서명 위조로 이어지는 비밀이 없습니다. omni 서명 개인키는 omni만 보관합니다.

Common Error Response#

{
  "error": {
    "code": "signature_mismatch",
    "message": "Request signature is invalid.",
    "request_id": "req_..."
  }
}

Error Codes#

HTTPCodeDescription
400invalid_request필수 field 누락 또는 형식 오류
401missing_signature서명 header 누락
401invalid_signaturesignature 형식 오류 또는 알 수 없는 key_id
401signature_mismatchsignature 검증 실패
401timestamp_out_of_rangetimestamp 허용 오차 초과
409nonce_reusednonce 재사용
409idempotency_conflict같은 idempotency key로 다른 body 요청
404bio_enrollment_not_found기존 등록 데이터 없음
400unsupported_finger지원하지 않는 finger code
400unsupported_bio_type지원하지 않는 bio type
500merchant_bio_internal_error가맹점 바이오 API 내부 오류

POST/enrollments#

등록 Flow에서 바이오 인증앱 callback으로 받은 바이오 등록 데이터를 가맹점 DB에 저장합니다.

재등록(overwrite) 정책: 같은 (merchant_user_ref + finger)로 다시 저장하면 기존 등록 데이터를 덮어씁니다(최신 등록이 유효). 재등록은 정상 시나리오(기기 변경, 품질 개선 등)입니다. 가맹점이 원하면 재등록 시 자체 추가 인증을 요구하는 것은 가맹점 재량입니다.

Method#

POST

Request#

{
  "merchant_id": "merchant_123",
  "merchant_user_ref": "user_abc",
  "provider_id": "pointlink",
  "enrollment_session_id": "enr_01J...",
  "type": "fingerprint",
  "finger": "LI",
  "features": "base64-or-provider-encoded-features",
  "enc_key": "base64-or-provider-encoded-key",
  "captured_at": "2026-07-02T13:03:00Z"
}
FieldTypeRequiredDescription
merchant_idstringyes가맹점 ID
merchant_user_refstringyes가맹점 로그인 사용자 참조값. omni에 전달되는 값만으로 개인을 특정할 수 없도록, 개인정보 원문이 아닌 가맹점 내부 불투명 참조값을 사용. 예: 전화번호 원문 대신 SHA-256(정규화된 전화번호) 값을 전달
provider_idstringyesMVP는 pointlink
enrollment_session_idstringyesomni 등록 세션 ID
typestringyesMVP는 fingerprint
fingerstringyesPointLink finger code
featuresstringyes바이오 인증앱이 생성한 바이오 등록 데이터
enc_keystringyes바이오 인증앱이 생성한 암호화 key
captured_atstringno앱 캡처 시각. 없으면 서버 수신 시각 사용 가능

Response 200#

{
  "stored": true,
  "merchant_user_ref": "user_abc",
  "provider_id": "pointlink",
  "type": "fingerprint",
  "finger": "LI",
  "enrollment_ref": "bio_enr_789",
  "stored_at": "2026-07-02T13:03:02Z"
}

Idempotency#

omni는 Idempotency-Key header에 enrollment_session_id를 보냅니다.

가맹점 바이오 API는 같은 Idempotency-Key와 같은 request body가 다시 들어오면 기존 저장 결과를 반환합니다. 같은 key로 다른 body가 들어오면 409 idempotency_conflict를 반환합니다.

Timeout and Retry#

omni client 기준:

request timeout: 5s (연결 수립 포함, 요청 전체 기준)
retry: network error, timeout, 5xx에 한해 1회
4xx: retry 없음

omni는 retry를 위해 features, enc_key를 DB나 durable queue에 저장하지 않습니다. retry는 현재 request memory 안에서만 수행합니다.

POST/enrollments/lookup#

확인 Flow에서 기존 등록 데이터를 조회합니다.

Method#

POST

조회 요청도 body를 사용합니다. merchant_user_ref를 URL path나 query에 넣지 않아 access log 노출 가능성을 줄입니다. 이 값 자체도 이메일, 전화번호, 이름, 주민등록번호 등 개인정보 원문이 아니어야 합니다.

Request#

{
  "merchant_id": "merchant_123",
  "merchant_user_ref": "user_abc",
  "provider_id": "pointlink",
  "verification_session_id": "ver_01J...",
  "type": "fingerprint",
  "finger": "LI"
}
FieldTypeRequiredDescription
merchant_idstringyes가맹점 ID
merchant_user_refstringyes가맹점 로그인 사용자 참조값. omni에 전달되는 값만으로 개인을 특정할 수 없도록, 개인정보 원문이 아닌 가맹점 내부 불투명 참조값을 사용. 예: 전화번호 원문 대신 SHA-256(정규화된 전화번호) 값을 전달
provider_idstringyesMVP는 pointlink
verification_session_idstringyesomni 확인 세션 ID
typestringyesMVP는 fingerprint
fingerstringyes조회할 finger code

Response 200#

{
  "found": true,
  "merchant_user_ref": "user_abc",
  "provider_id": "pointlink",
  "type": "fingerprint",
  "finger": "LI",
  "features": "base64-or-provider-encoded-features",
  "enc_key": "base64-or-provider-encoded-key",
  "enrollment_ref": "bio_enr_789",
  "enrolled_at": "2026-07-02T12:30:00Z"
}

Response 404#

{
  "error": {
    "code": "bio_enrollment_not_found",
    "message": "Bio enrollment was not found.",
    "request_id": "req_..."
  }
}

Security Notes#

  • 응답의 features, enc_key는 omni API 서버가 확인 처리 중에만 사용합니다.
  • omni는 조회 응답의 바이오 payload를 DB, durable queue, 로그에 저장하지 않습니다.
  • 가맹점은 필요한 사용자와 finger에 해당하는 최소 등록 데이터만 반환합니다.