홈 › B2B API › 개발자 문서

VERIT Anti-Scalping API v1 · Base https://verit.teamplut.com/api/b2b

시작하기 — 샌드박스 키

키는 한 번만 표시됩니다. 샌드박스 키(vk_sandbox_)로 만든 표는 실제 입장에 쓸 수 없고, 나머지 동작은 라이브와 같습니다. 라이브 키(vk_live_)는 도입 계약 뒤 발급합니다.

인증

Authorization: Bearer vk_sandbox_… # 모든 /api/b2b/* 요청 Content-Type: application/json

키는 서버에 해시로만 저장됩니다. 잃어버리면 새로 발급받으세요. 전화번호는 파트너별 HMAC 으로 바뀌어 저장되며 원문은 남지 않습니다.

기본 흐름

# 1) 결제 승인 직후 — 표를 사람에게 묶어 발권 POST /events {"ext_event":"SHOW-2026-1024","title":"서율 ARENA","at":"2026-10-24T19:00","cap_per_person":4} POST /tickets/issue {"ext_event":"SHOW-2026-1024","phone":"010-1234-5678","qty":2,"seats":["R 3열 12번","R 3열 13번"]} → {"tickets":[{"code":"VT-1A2B3C4D5E6F","wallet_url":"https://verit.teamplut.com/w/VT-…"}, …]} # 2) 관객에게는 wallet_url 만 문자로. 화면이 30초마다 QR 을 새로 그린다. # 3) 입장 — 스캐너가 QR 값을 그대로 보낸다 POST /gate/verify {"qr":"VR1:VT-1A2B3C4D5E6F:482913"} → 200 {"ok":true,"ticket":{"seat":"R 3열 12번"}} 또는 409 {"ok":false,"reason":"만료된 코드 — 캡처·전달된 화면"} # 4) 양도 — 정가 이하만 통과, 옛 코드는 즉시 무효 POST /tickets/VT-1A2B3C4D5E6F/rebind {"phone":"010-9876-5432","price":132000,"face":132000}

리스크 점수 POST/risk/score

예매 버튼을 누른 «한 번의 시도»를 넣으면 0~100 점과 결정을 돌려줍니다. 60 이상 block, 30~59 slow(대기열 뒤로), 그 아래 allow. 점수는 같은 파트너 안에서 축적되는 기록으로 계산되므로 첫 호출부터 의미가 있습니다.

POST /risk/score {"phone":"010-1234-5678","device_id":"fp_9f3…","event_id":"SHOW-2026-1024","account_age_min":1,"prior_qty":0} → {"score":35,"decision":"slow","reasons":["velocity:10분 내 시도 7회","account:가입 3분 이내"],"policy":{"max_per_person":4,"block_at":60,"slow_at":30}}

어떤 신호를 어떻게 합치는지는 공개하지 않습니다. reasons 는 운영 참고용 라벨입니다. phone 대신 자체 subject_id 를 보내도 됩니다.

이벤트 · 대기열

POST/events

멱등 등록. ext_event(귀사 ID), title, at, cap_per_person(기본 4).

POST/queue/token

{ext_event, subject_id}{token, ahead, admitted}. 같은 사람이 다시 부르면 같은 토큰. 앞사람이 없으면 즉시 admitted.

발권

POST/tickets/issue

{ext_event, phone, qty, seats[]}. 같은 사람이 한도(cap_per_person)를 넘기면 403. 응답의 wallet_url 을 관객에게 보내면 끝입니다.

POST/tickets/{code}/void

환불·취소 시 무효화.

동적 QR

GET/tickets/{code}/code

{qr, svg, expires_in, step}. 자체 앱에 QR 을 그리려면 expires_in 마다 다시 부르세요. 값은 서버가 만들며 관객 기기 시계를 믿지 않습니다.

입장 검증

POST/gate/verify

{qr} → 200 ok / 409 {reason}. 거부 사유: 없는 표 · 이미 입장함 · 사용할 수 없는 표 · 만료된 코드(캡처) · 다른 파트너의 표. 거부도 전부 기록되어 리포트에 잡힙니다.

정가 양도

POST/tickets/{code}/rebind

{phone, price, face}. price > face 면 403 — 이 한 줄이 암표 제로의 규칙입니다. 통과하면 옛 코드는 transferred 로 죽고 새 코드가 새 사람에게 붙습니다.

증빙 리포트

GET/events/{ext_event}/report

→ 발권·입장·양도·취소 수, 스캔 결과별 집계(ok / 만료 코드 / 중복 …), 리스크 결정별 집계(allow / slow / block). 주최사 정산·보고서에 그대로 씁니다.

v2 · 역방향 입장 (게이트를 관객이 찍는다)

정방향(관객 QR 을 스태프가 스캔) 대신, 입구 화면이 게이트 QR 을 보여 주고 관객 앱이 그걸 찍어 보냅니다. 입장 자격이 그 순간 처음 만들어지므로 공연 전에는 팔 물건이 없습니다. 좌석이 없는 표(구역만 산 표)는 이때 좌석이 정해집니다.

GET/gate/nonce?ext_event=

{qr, svg, code, expires_in, step}. 입구 화면(태블릿·TV)이 expires_in 마다 새로 그립니다.

POST/admit

{gate_qr, phone} — 관객 앱이 찍은 게이트 QR 과 관객 계정 번호. → {admitted:[{code, seat, confirm}]}. confirm 4자리가 관객 화면과 스태프 화면에 같이 뜹니다. 지난 코드(캡처)는 409.

공정성 원장

GET/ledger

발권·양도·입장·환매·대기 제안이 해시 체인으로 이어진 기록. 개인정보 없이 해시·좌석·시각만. 누구나 GET https://verit.teamplut.com/api/ledger/verify 로 체인 무결성을 검증합니다. «왜 저 사람이 먼저였나»에 답이 있습니다.

출석 우선권

POST/priority

{phone}{attendance, tier, suggested_queue_bucket}. 실제 입장(entered) 횟수만 셉니다. 발권·결제는 세지 않습니다. 대기열 앞 구간을 이 값으로 주면, 우선권은 돈으로도 봇으로도 살 수 없게 됩니다.

이 밖에 문서에 적지 않는 비공개 차단 계층이 서버 안에서 항상 함께 돌아갑니다. 파트너 호출에도 같은 계층이 적용됩니다.

Sentinel — 매크로 차단 SDK

파트너 예매 페이지에 스크립트 한 줄을 붙이면, 관객 브라우저가 사람인지 «조용히» 판정한 토큰을 만듭니다. 파트너 서버는 예매 요청에 실린 토큰을 아래 API 로 검증하고 allow / slow / block 을 받습니다. 판정 원리는 공개하지 않으며 SDK 는 «무엇을 재는지»만 담고 있습니다.

<!-- 파트너 예매 페이지 --> <script>window.SENTINEL_BASE='https://verit.teamplut.com'</script> <script src="https://verit.teamplut.com/assets/sentinel.js"></script> <script> Sentinel.slider(document.getElementById('human'), function(f){ window._fitts = f; }); // 확보 버튼 앞에 «옮겨서 확보하기» 슬라이더 document.getElementById('reserve').onclick = function(){ Sentinel.ready(window._fitts).then(function(token){ /* 예매 요청에 token 을 실어 파트너 서버로 */ }); }; </script>
POST/sentinel/verify

{token}{valid, human 0-100, decision allow|slow|block, device}. 토큰은 10분, 기기에 묶여 있습니다.

GET/sentinel/stats

최근 24시간 판정 수·평균·허용·차단.

규모가 큰 오픈은 파트너 센터에서 «Sentinel 강화 모드»를 켜면 그 오픈에만 판정 강도가 올라갑니다.

하드웨어 결속 — 표를 폰 안에

베릿 지갑은 관객 폰의 보안칩(Secure Enclave·StrongBox·TEE)에 키를 만들어 표를 묶습니다. 개인키는 칩 밖으로 나오지 않고, 역방향 입장 요청에는 그 칩의 생체 서명이 실려야 합니다. 파트너가 따로 구현할 것은 없습니다 — 지갑이 처리합니다. 유심 결속은 네이티브 앱에서 아래 어댑터로 더합니다.

POST/hw/register/options · /hw/register

WebAuthn 플랫폼 인증기 등록(관객 세션). attestation: none, userVerification: required. 등록되면 그 계정의 /admitassertion 필드가 없으면 428 로 거부됩니다.

POST/hw/assert/options

입장 직전 서명 챌린지. 3분 유효, 한 번만 쓰입니다. 서명 카운터가 되돌아가면 복제 키로 보고 표를 격리합니다.

POST/hw/sim

유심 어댑터(네이티브 SDK). {attest:{carrier, sub_hash, device, ts}, sig}sub_hash 는 앱이 SubscriptionManager(Android)·CoreTelephony(iOS, 통신사 코드까지) 로 얻은 가입자 식별자의 SHA-256, sig 는 SDK 키 HMAC. 원본 ICCID 는 서버에 오지 않습니다. 같은 계정에 다른 유심이 붙는 순간 보유 표가 격리되고 원장에 simchange 가 남습니다.

GET/hw/keys · /hw/sim

이 계정의 결속 상태. 파트너 리포트에는 입장별 hw: true|false 로 집계됩니다.

iOS 는 앱에서도 ICCID 를 읽을 수 없어 유심 결속은 통신사 코드 + 보안칩 키 조합으로, Android 는 가입자 식별자 해시로 묶입니다. 웹 지갑만 쓰는 관객은 보안칩 결속까지 적용됩니다.

오류

401 {"ok":false,"error":"invalid_api_key"} 403 {"ok":false,"message":"정가를 넘는 양도는 거부합니다. …"} 404 {"ok":false,"message":"먼저 /events 로 이벤트를 등록해 주세요."} 409 {"ok":false,"reason":"이미 입장함 2026-10-24T10:02:11Z"} 429 {"ok":false,"message":"잠시 뒤 다시 시도해 주세요."}

OpenAPI 3.0 JSON 스캐너 화면 써 보기 도입 문의