ViaLink

시작하기

ViaLink 를 처음 접하신다면 아래 질문부터 살펴보세요. 더 자세한 동작은 아래 REST API · SDK 가이드에서 확인할 수 있습니다.

ViaLink 는 어떤 서비스인가요?

앱·웹·광고 캠페인의 모든 클릭을 짧은 링크 하나로 만들고, 그 링크가 어디서 클릭되어 어떤 사용자가 앱을 설치·결제했는지까지 추적해 주는 딥링크 / 어트리뷰션 인프라입니다.

Android App Links 와 iOS Universal Links 의 도메인 인증 파일 자동 호스팅, 앱 미설치 사용자를 위한 디퍼드 딥링킹(Deferred Deep Linking), 클릭→설치→결제까지의 어트리뷰션을 한 곳에서 제공합니다.

기존 단축 URL(bit.ly 등) 과 무엇이 다른가요?

단축 URL 은 "어디로 보낼지" 만 결정합니다. ViaLink 는 추가로 다음을 자동 처리합니다.

  • OS / 브라우저 분기 (iOS · Android · Web)
  • 앱이 설치된 사용자는 앱으로, 미설치 사용자는 스토어로 라우팅
  • 설치 후 첫 실행 시 원래 의도한 화면으로 보내는 디퍼드 딥링킹
  • 클릭·설치·결제 매칭을 광고 매체 / 캠페인 단위로 집계하는 어트리뷰션
어떤 플랫폼을 지원하나요?

공식 SDK 6종을 제공합니다. REST API 만으로도 모든 기능을 사용할 수 있습니다.

  • Android — Gradle 좌표 com.vialink:sdk
  • iOS — SPM product ViaLinkCore
  • Web — npm vialink-web-sdk
  • React Native — npm vialink-react-native-sdk
  • Unity — UPM (GitHub URL 설치)
  • Flutter — pub.dev vialink_flutter_plugin
가입과 첫 앱 등록은 어떻게 하나요?

대시보드에 가입한 뒤 앱을 추가하면 즉시 apiKeyapiSecret 이 발급됩니다. 이후 SDK 가이드의 초기화 코드에 키를 넣으면 바로 통합을 시작할 수 있습니다.

API Key / 인증

API Key 는 어디서 발급받나요?

대시보드의 앱 → SDK 탭에서 확인할 수 있습니다. 앱을 만들면 자동으로 발급되므로 별도 신청 절차가 없습니다.

API Key 와 API Secret 의 차이는 뭔가요?

API Key 는 SDK·클라이언트에서 사용하는 공개 식별자이고, API Secret 은 서버 간(S2S) 호출에만 쓰는 비공개 키입니다. Secret 은 절대 클라이언트(앱·웹) 코드에 포함시키지 마세요.

저장 측면에서도 Key 는 평문(UUID), Secret 은 bcrypt 해시로 저장되어 서버에서도 원본을 다시 열람할 수 없습니다.

어떤 엔드포인트에 Secret 이 필요한가요?

다음 두 S2S 엔드포인트만 Secret 이 필요합니다.

  • POST /v1/payments/succeeded
  • POST /v1/payments/failed

그 외 SDK 호출(/v1/resolve, /v1/deferred-match, /v1/events, /v1/payments/initiated 등) 은 X-API-Key 만으로 동작합니다.

키를 노출했어요. 재발급은 어떻게 하나요?

대시보드의 SDK 탭에서 API Secret 재발급 버튼으로 즉시 재발급할 수 있습니다.

주의: grace period 가 없습니다. 재발급 즉시 기존 Secret 은 무효화되므로, 운영 중인 서버가 있다면 새 Secret 을 먼저 배포하고 재발급을 진행하세요.

API Key 는 계정 단위인가요, 앱 단위인가요?

앱(테넌트) 단위입니다. 한 계정으로 여러 앱을 만들 수 있고, 각 앱마다 별도의 Key / Secret 이 발급됩니다.

인증 헤더 이름의 대소문자가 중요한가요?

HTTP 헤더는 대소문자를 구분하지 않습니다. X-API-Key · x-api-key 모두 정상 인식됩니다.

SDK 통합

AppLinks / Universal Links 도메인 인증 파일은 직접 호스팅해야 하나요?

직접 호스팅할 필요가 없습니다. Bridge Server 가 두 파일을 모두 자동 호스팅합니다.

  • AndroidGET /.well-known/assetlinks.json. 등록된 패키지명(Play Store · ONE Store) 과 SHA-256 fingerprint 로 동적 생성
  • iOSGET /.well-known/apple-app-site-association. Bundle ID · Team ID · slug 로 동적 생성

대시보드의 앱 설정에서 Bundle ID / SHA-256 만 등록하면 자동으로 반영됩니다.

/v1/resolve 와 /v1/deferred-match 의 차이는?
  • POST /v1/resolve — App Links / Universal Links 로 앱이 직접 열렸을 때 short code 로 링크 데이터를 즉시 조회 (Redis 캐시 우선)
  • POST /v1/deferred-match — 앱 첫 실행 시 이전 클릭을 fingerprint 로 매칭 (디퍼드 딥링킹)

SDK 통합 가이드의 표준 흐름을 따르면 두 호출이 자동으로 처리됩니다.

이벤트는 어떻게 기록하나요?
  • POST /v1/events — 단일 이벤트
  • POST /v1/events/batch — 배치 이벤트, 요청당 최대 100개 (오프라인 큐잉에 활용)

표준 이벤트명은 서버에서 별도 검증하지 않습니다. signup, purchase, app.open 등 원하는 이름으로 자유롭게 정의할 수 있습니다.

결제 / 어트리뷰션

결제 어트리뷰션은 어떻게 흐르나요?
  • SDK 가 POST /v1/payments/initiated 로 결제 시도 전송 (X-API-Key)
  • PG 콜백을 받은 백엔드가 POST /v1/payments/succeeded 또는 /failed 호출 (X-API-Key + X-API-Secret)
  • terminal 호출 시점에 동기로 매칭 수행 — payment_events 테이블의 attributed_* 컬럼에 즉시 결과 기록

비동기 배치 잡이 아니므로 호출 응답에서 곧바로 어트리뷰션 결과를 확인할 수 있습니다.

어트리뷰션 모델은 무엇인가요?

마지막-클릭(last_click) 모델을 사용합니다. 동일 device 의 가장 최근 click 이 어트리뷰션 대상입니다.

어트리뷰션 윈도우 기본값은?
  • 클릭 → 설치 매칭: 7일 (attribution.click_to_install_days)
  • 설치 → 결제 매칭: 30일 (attribution.install_to_purchase_days)
  • initiated → succeeded/failed 매칭: 24시간 (attribution.initiated_to_succeeded_hours)

세 값 모두 어드민의 시스템 설정에서 조정할 수 있습니다. 자세한 의사코드는 아래 어트리뷰션 정책 섹션을 참고하세요.

매칭이 실패하면 어떻게 되나요?

호출은 그대로 200 으로 응답되지만, attributed.method 가 다음 값 중 하나로 기록됩니다.

  • no_initiated_or_no_device — initiated 호출이 없거나 device_id 누락
  • no_install_in_window — 윈도우 안에 동일 device 의 app.install 이벤트가 없음
  • organic_install — 설치는 잡혔으나 link_id 가 없는 오가닉 설치
  • last_click_install_only — 설치는 매칭되지만 click 이벤트가 없음
  • error — DB 조회 실패 (텔레그램 알림 발송)

각 케이스의 의미와 매칭 단계는 어트리뷰션 정책 섹션의 의사코드를 참고하세요.

요금제 / 운영

어떤 요금제가 있나요?

다음 6개 플랜이 있습니다.

  • Free
  • Plus
  • Premium
  • Pro
  • Ultra
  • Enterprise

각 플랜별 월 크레딧·API 호출 수·앱 수·딥링크 수 한도와 사용 가능한 기능(customDomain, customLanding, teamInvite, paymentTracking, customEvent) 은 대시보드 요금제 페이지에서 확인할 수 있습니다.

분석 데이터는 어디서 보나요?

대시보드의 앱 → 분석 페이지에서 클릭, 설치, 플랫폼별, 캠페인별 통계를 확인할 수 있습니다.

인증

모든 API 호출은 X-API-Key 헤더로 인증합니다. 대시보드에서 앱을 등록하면 API Key가 발급됩니다.

요청 헤더

코드가 복사되었습니다
X-API-Key: <your_api_key>

Base URL

모든 API 요청의 기본 URL입니다.

코드가 복사되었습니다
https://vialink.app

Short Code 중복 확인

GET/api/links/check-short-code

링크 생성 전 customCode가 사용 가능한지 실시간으로 확인합니다. 인증: 세션 쿠키 + 해당 테넌트의 ADMIN 이상 권한 필요.

쿼리 파라미터

tenantIdstring필수
앱(테넌트) ID
codestring필수
중복 확인할 short code 문자열

요청 예시

코드가 복사되었습니다
GET /api/links/check-short-code?tenantId=tenant_abc&code=summer-sale-2026

응답 (200 OK — 사용 가능)

코드가 복사되었습니다
{
  "available": true
}

응답 (200 OK — 이미 사용 중)

코드가 복사되었습니다
{
  "available": false,
  "reason": "이미 사용 중인 short code 입니다."
}

Short Code 정책

  • · 자동 생성: Base36 소문자(a–z, 0–9) 6자. (구 Base62 대소문자 혼합에서 변경)
  • · 사용자 지정: /^[a-z0-9_-]{3,24}$/ — 소문자 영문, 숫자, 하이픈, 언더스코어, 3~24자.
  • · 라우팅 case-insensitive: vialink.app/\{slug\}/PTMLWTvialink.app/\{slug\}/ptmlwt는 동일 링크를 매칭.

결제 시도 (SDK)

POST/v1/payments/initiated

사용자가 결제창을 띄우는 시점에 SDK가 호출하는 엔드포인트입니다. 같은 order_id로 여러 번 호출되어도 새 row가 생성됩니다 (사용자 카드 오입력 → 재시도 케이스 추적). 매칭은 이 단계에서 수행하지 않고, 이후 /v1/payments/succeeded가 호출될 때 동기로 매칭됩니다.

요청 헤더

코드가 복사되었습니다
X-Api-Key: <your_api_key>
Content-Type: application/json

요청 파라미터

order_idstring필수
주문 식별자. 1~100자, 영문/숫자/하이픈/언더스코어 허용 (^[A-Za-z0-9_-]{1,100}$).
amountnumber필수
결제 금액. 0보다 큰 숫자. 통화 단위 그대로 (예: KRW=원화 단위, USD=달러 단위).
currencystring필수
ISO 4217 통화 코드 (대문자). SystemConfig payments.allowed_currencies에 등록된 값만 허용.
link_idnumber선택
결제와 직접 연관된 링크 ID. 어트리뷰션은 device_id 기반 install/click 추적으로 별도 수행.
payment_methodstring선택
결제 수단 (card / transfer / kakaopay 등). 50자까지 저장.
device_infoobject선택
디바이스 정보. SDK가 자동 수집 (device_id, country 등). 어트리뷰션 매칭에 사용됨.
metadataobject선택
자유 형식 추가 데이터. JSON object 그대로 저장.

요청 예시

코드가 복사되었습니다
POST /v1/payments/initiated
X-Api-Key: <your_api_key>
Content-Type: application/json

{
  "order_id": "ORD-2026-0001",
  "amount": 19900,
  "currency": "KRW",
  "payment_method": "card",
  "device_info": { "device_id": "abc-123", "country": "KR" },
  "metadata": { "product_id": "prod-001" }
}

응답 (200 OK)

코드가 복사되었습니다
{
  "success": true,
  "payment_event_id": "9876543210"
}

에러 응답

400Bad Request선택
입력 형식 오류. 예: { "error": "order_id가 올바르지 않습니다 (1~100자, 영문/숫자/하이픈/언더스코어)." }
401Unauthorized선택
헤더 누락. { "error": "API Key가 필요합니다." }
403Forbidden선택
유효하지 않은 API Key 또는 비활성 테넌트. { "error": "유효하지 않은 API Key입니다." }
500Server Error선택
서버 내부 오류. { "error": "서버 오류가 발생했습니다." }

결제 성공 (S2S)

POST/v1/payments/succeeded

고객사 백엔드가 PG 콜백을 수신한 뒤 ViaLink 서버를 호출하는 Server-to-Server 엔드포인트입니다. 매칭 파이프라인이 동기로 실행되어 응답에 attributed 객체가 포함됩니다. SDK에서 직접 호출하지 마세요 (apiSecret이 노출됩니다).

요청 헤더

코드가 복사되었습니다
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/json

apiSecret은 DB에 bcrypt hash로 저장되며 평문 비교가 아닌 bcrypt.compare로 검증됩니다. 인증 실패는 단일 메시지 "인증에 실패했습니다."로 통합되어 (apiKey 존재 여부 / apiSecret 일치 여부 / 테넌트 활성 상태) 어느 단계에서 실패했는지 노출되지 않습니다.

요청 파라미터

order_idstring필수
결제 시도 단계와 동일한 주문 식별자. 1~100자, 영문/숫자/하이픈/언더스코어.
amountnumber필수
실제 결제 금액. 0보다 큰 숫자.
currencystring필수
ISO 4217 통화 코드 (대문자).
transaction_idstring선택
PG사 거래번호. 200자까지 저장.
payment_methodstring선택
결제 수단. 50자까지 저장.
metadataobject선택
자유 형식 추가 데이터. JSON object.

요청 예시

코드가 복사되었습니다
curl -X POST https://vialink.app/v1/payments/succeeded \
  -H "X-Api-Key: <your_api_key>" \
  -H "X-Api-Secret: <your_api_secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ORD-2026-0001",
    "amount": 19900,
    "currency": "KRW",
    "transaction_id": "PG-20260428-9988",
    "payment_method": "card",
    "metadata": { "channel": "checkout-v2" }
  }'

응답 (200 OK, 정상)

코드가 복사되었습니다
{
  "success": true,
  "payment_event_id": "9876543220",
  "attributed": {
    "matched": true,
    "method": "last_click",
    "link_id": 12345,
    "click_event_id": 678901,
    "install_event_id": "5544332211"
  }
}

응답 (200 OK, idempotent — 같은 status 재호출)

코드가 복사되었습니다
{
  "success": true,
  "payment_event_id": "9876543220",
  "idempotent": true,
  "attributed": {
    "matched": true,
    "method": "last_click",
    "link_id": 12345,
    "click_event_id": 678901,
    "install_event_id": "5544332211"
  }
}

응답 (409 Conflict — 다른 terminal status 충돌)

동일 order_id가 이미 다른 terminal 상태(예: failed, refunded, canceled)로 존재할 때 발생합니다.

코드가 복사되었습니다
{
  "error": "payment already in terminal state",
  "existing_status": "failed"
}

attribution_method 값

attributed.method가 가질 수 있는 7가지 값입니다.

last_clickmatched선택
installed → click이 모두 매칭되었습니다 (가장 정상 케이스). link_id, click_event_id, install_event_id 모두 채워집니다.
last_click_install_onlymatched선택
install은 매칭되었지만 click은 윈도우 내에 없습니다 (예: 사전 설치된 앱이 push로 활성화). link_id, install_event_id 채워지고 click_event_id는 null.
organic_installunmatched선택
install은 있지만 link_id가 null입니다 (오가닉 설치). install_event_id만 채워집니다.
no_install_in_windowunmatched선택
initiated 단계의 device_id로 install_to_purchase_days 윈도우 안에 install 이벤트가 없습니다.
no_initiated_or_no_deviceunmatched선택
initiated row가 없거나 device_id가 비어 있어 매칭 시도 자체를 할 수 없습니다.
errorunmatched선택
DB 조회 실패 등 매칭 파이프라인 내부 오류. 응답은 200으로 정상 반환되며 error 로그는 별도 기록됩니다.
unmatchedunmatched선택
기본값/예비. 실제로는 위 6가지 분기 중 하나로 응답됩니다.

에러 응답

400Bad Request선택
입력 형식 오류 (order_id 패턴, amount 범위, currency 미허용 등).
401Unauthorized선택
X-Api-Key 또는 X-Api-Secret 헤더 누락. { "error": "API Key가 필요합니다." } 또는 { "error": "API Secret이 필요합니다." }
403Forbidden선택
인증 실패 (apiKey/apiSecret 불일치, 비활성 테넌트). { "error": "인증에 실패했습니다." } — 세부 사유는 서버 로그에만 기록.
409Conflict선택
다른 terminal status가 이미 존재. existing_status 필드로 어떤 상태인지 안내.
500Server Error선택
서버 내부 오류.

결제 실패 (S2S)

POST/v1/payments/failed

PG 결제 실패 webhook을 받은 뒤 고객사 백엔드가 호출합니다. 퍼널 분석을 위해 매칭은 동일하게 수행되어 DB에 저장되지만 응답에는 attributed 객체를 노출하지 않습니다 (실패 케이스에서 클릭/설치 매칭 결과를 클라이언트에 전달할 의미가 없기 때문).

요청 헤더

코드가 복사되었습니다
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/json

요청 파라미터

order_idstring필수
결제 시도 단계와 동일한 주문 식별자.
amountnumber필수
시도된 결제 금액. 0보다 큰 숫자.
currencystring필수
ISO 4217 통화 코드 (대문자).
failure_reasonstring선택
PG에서 받은 실패 사유. 500자까지 저장.
transaction_idstring선택
PG사 거래번호. 200자까지 저장.
payment_methodstring선택
결제 수단. 50자까지 저장.
metadataobject선택
자유 형식 추가 데이터.

요청 예시

코드가 복사되었습니다
curl -X POST https://vialink.app/v1/payments/failed \
  -H "X-Api-Key: <your_api_key>" \
  -H "X-Api-Secret: <your_api_secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ORD-2026-0001",
    "amount": 19900,
    "currency": "KRW",
    "failure_reason": "card_declined",
    "transaction_id": "PG-20260428-9988",
    "payment_method": "card"
  }'

응답 (200 OK)

코드가 복사되었습니다
{
  "success": true,
  "payment_event_id": "9876543230"
}

응답 (200 OK, idempotent)

같은 order_idfailed가 다시 호출되면 멱등 처리됩니다 (PG webhook 재시도 대응).

코드가 복사되었습니다
{
  "success": true,
  "payment_event_id": "9876543230",
  "idempotent": true
}

응답 (409 Conflict)

이미 succeeded/refunded/canceled인 주문에 failed 호출 시 발생합니다.

코드가 복사되었습니다
{
  "error": "payment already in terminal state",
  "existing_status": "succeeded"
}

에러 응답

400Bad Request선택
입력 형식 오류.
401Unauthorized선택
X-Api-Key 또는 X-Api-Secret 헤더 누락.
403Forbidden선택
인증 실패. { "error": "인증에 실패했습니다." }
409Conflict선택
다른 terminal status로 이미 존재.
500Server Error선택
서버 내부 오류.

어트리뷰션 정책

결제 성공/실패가 어떤 클릭/설치에 귀속되는지 결정하는 규칙입니다. 마지막-클릭(last_click) 모델을 따르며, /v1/payments/succeeded 또는 /v1/payments/failed가 호출되는 시점에 동기로 매칭이 수행됩니다(eager). 매칭 결과는 payment_events 테이블의 attributed_* 컬럼에 즉시 기록됩니다.

윈도우 설정

어트리뷰션 윈도우는 SystemConfig에 저장되며 어드민 system-config 메뉴에서 조정할 수 있습니다.

attribution.click_to_install_daysinteger (days)선택
클릭 → 설치 매칭 윈도우. 설치 발생 시점 기준으로 N일 이내의 클릭만 어트리뷰션 후보. 기본값 7.
attribution.install_to_purchase_daysinteger (days)선택
설치 → 결제 매칭 윈도우. 결제 terminal 시점 기준으로 N일 이내의 설치만 어트리뷰션 후보. 기본값 30.
attribution.initiated_to_succeeded_hoursinteger (hours)선택
initiated → succeeded/failed 매칭 윈도우. terminal 시점 기준 N시간 이내의 initiated row만 device_id 추출에 사용. 기본값 24.
payments.allowed_currenciesstring[] (CSV)선택
허용 통화 화이트리스트. ISO 4217 대문자. 기본값 KRW,USD,EUR,JPY 등 (시스템 기본값 참조).

매칭 단계 (의사코드)

코드가 복사되었습니다
function attributePayment(tenant_id, order_id, terminal_at):
  cfg = loadAttributionConfig()  // SystemConfig 조회

  # 1) initiated row 찾기 (가장 최근, terminal_at - initiated_to_succeeded_hours 이내)
  init = paymentEvent.findFirst({
    tenantId, orderId, status: "initiated",
    createdAt >= terminal_at - cfg.initiated_to_succeeded_hours
  })
  if init == null or init.deviceId == null:
    return method="no_initiated_or_no_device"

  # 2) install 이벤트 찾기 (device_id 기준, terminal_at - install_to_purchase_days 이내)
  install = sdkEvent.findFirst({
    tenantId, deviceId: init.deviceId,
    eventName: "app.install",
    createdAt >= terminal_at - cfg.install_to_purchase_days
  })
  if install == null:
    return method="no_install_in_window"
  if install.linkId == null:
    return method="organic_install" (install_event_id만 채워짐)

  # 3) click 이벤트 찾기 (link_id 기준, install 직전 click_to_install_days 이내)
  click = clickEvent.findFirst({
    tenantId, linkId: install.linkId,
    createdAt <= install.createdAt,
    createdAt >= install.createdAt - cfg.click_to_install_days
  })

  return method = click ? "last_click" : "last_click_install_only"

매칭 실패 케이스

no_initiated_or_no_deviceunmatched선택
initiated 호출이 없었거나 SDK가 device_id를 보내지 않은 경우. 가장 흔한 원인: SDK 미초기화 / initiated_to_succeeded_hours 윈도우 초과.
no_install_in_windowunmatched선택
동일 device_id의 app.install 이벤트가 install_to_purchase_days 이내에 없음. SDK 설치 추적 누락 또는 윈도우 초과.
organic_installunmatched선택
설치는 잡혔으나 link_id가 null (스토어 직접 검색 등 오가닉).
last_click_install_onlymatched선택
설치는 매칭되지만 click_to_install_days 안에 click 이벤트가 없음 (예: 사전 설치 후 푸시 활성화).
errorunmatched선택
DB 조회 실패. 응답은 200이지만 attributed.method = error로 표시되고 텔레그램 알림 발송.
unmatchedunmatched선택
이론상 도달하지 않는 fallback. 위 케이스로 분기되도록 설계되어 있음.
last_clickmatched선택
유일한 정상 매칭 케이스. link_id, click_event_id, install_event_id 모두 채워짐.

Android SDK

API 24 (7.0)+, Kotlin 1.9+ · Gradle

설치

Gradle에 의존성을 추가합니다.

코드가 복사되었습니다
// build.gradle.kts (app)
dependencies {
    implementation("com.vialink:sdk:1.0.0")
}

초기화

Application.onCreate()에서 SDK를 초기화합니다.

코드가 복사되었습니다
// Application.kt
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        ViaLinkSDK.init(this, "YOUR_API_KEY")
    }
}

이벤트 추적

커스텀 이벤트를 추적합니다. SDK가 30초마다 배치로 전송합니다.

코드가 복사되었습니다
// 구매 완료
ViaLinkSDK.track("purchase", mapOf(
    "product_id" to "12345",
    "revenue" to 29900,
    "currency" to "KRW"
))

// 회원가입
ViaLinkSDK.track("signup")

// 장바구니 추가
ViaLinkSDK.track("add_to_cart", mapOf("product_id" to "12345"))

결제 추적

사용자 결제 시도를 ViaLink 서버(/v1/payments/initiated)로 전송합니다. payment_method/metadata는 옵션이며 device_info는 SDK가 자동 수집합니다. 응답의 paymentEventId로 동일 order_id의 결제 라이프사이클을 식별할 수 있습니다. 고객사 백엔드는 PG 콜백 수신 후 별도로 /v1/payments/succeeded 또는 /failed를 호출해야 합니다 (S2S 인증 + bcrypt 검증). SDK 1.1.1+ 부터 사용 가능합니다.

코드가 복사되었습니다
import com.vialink.sdk.ViaLinkSDK
import com.vialink.sdk.model.PaymentInitiatedArgs
import kotlinx.coroutines.launch

// 결제창 띄우기 직전 (Coroutine scope 안에서)
lifecycleScope.launch {
    try {
        val result = ViaLinkSDK.payment.initiated(
            PaymentInitiatedArgs(
                orderId = "ORD-2026-0001",
                amount = 19900.0,
                currency = "KRW",
                paymentMethod = "card",
                metadata = mapOf("productId" to "prod-001"),
            )
        )
        // result.success, result.paymentEventId
        Log.d("ViaLink", "payment_event_id=${result.paymentEventId}")
    } catch (e: IllegalArgumentException) {
        // 입력 검증 실패 (orderId 형식, amount, currency)
    } catch (e: Exception) {
        // 네트워크 오류
    }
}

iOS SDK

iOS 15.0+, Swift 5.9+ · Swift Package Manager

설치

Xcode에서 Swift Package를 추가합니다.

코드가 복사되었습니다
Xcode > File > Add Package Dependencies
URL: https://github.com/aresjoydev/vialink-ios-sdk

초기화

AppDelegate 또는 SwiftUI App의 init에서 SDK를 초기화합니다.

코드가 복사되었습니다
import ViaLinkCore

// AppDelegate
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions ...) -> Bool {
    ViaLinkSDK.shared.configure(apiKey: "YOUR_API_KEY")
    return true
}

// 또는 SwiftUI App
@main
struct MyApp: App {
    init() {
        ViaLinkSDK.shared.configure(apiKey: "YOUR_API_KEY")
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    ViaLinkSDK.shared.handleURL(url)
                }
        }
    }
}

이벤트 추적

커스텀 이벤트를 추적합니다.

코드가 복사되었습니다
// 구매 완료
ViaLinkSDK.shared.track("purchase", data: [
    "product_id": "12345",
    "revenue": "29900",
    "currency": "KRW"
])

// 회원가입
ViaLinkSDK.shared.track("signup")

결제 추적

사용자 결제 시도를 ViaLink 서버(/v1/payments/initiated)로 전송합니다. async/await 기반이며 PaymentError로 입력 검증 실패와 네트워크 오류를 분리해서 처리할 수 있습니다. 고객사 백엔드는 PG 콜백 수신 후 별도로 /v1/payments/succeeded 또는 /failed를 호출해야 합니다 (S2S 인증 + bcrypt 검증). SDK 1.1.1+ 부터 사용 가능합니다.

코드가 복사되었습니다
import ViaLinkCore

// 결제창 띄우기 직전 (async 컨텍스트 안에서)
Task {
    do {
        let result = try await ViaLinkSDK.shared.payment.initiated(
            PaymentInitiatedArgs(
                orderId: "ORD-2026-0001",
                amount: 19900,
                currency: "KRW",
                paymentMethod: "card",
                metadata: ["productId": "prod-001"]
            )
        )
        print("payment_event_id=\(result.paymentEventId)")
    } catch let error as PaymentError {
        // .invalidOrderId / .invalidAmount / .invalidCurrency / .sdkNotInitialized / .networkFailure
    } catch {
        // 기타 오류
    }
}

Web SDK

Chrome 80+, Safari 14+, Firefox 78+ · npm / CDN

설치

npm 또는 CDN으로 설치합니다.

코드가 복사되었습니다
npm install vialink-web-sdk

초기화

SDK를 초기화합니다. 페이지 이탈 시에도 이벤트가 전송됩니다.

코드가 복사되었습니다
import { ViaLinkWebSDK } from 'vialink-web-sdk';

const sdk = ViaLinkWebSDK.init({
  apiKey: 'YOUR_API_KEY'
});

이벤트 추적

커스텀 이벤트를 추적합니다. 30초 배치 전송 + 페이지 이탈 시 sendBeacon으로 전송합니다.

코드가 복사되었습니다
// 구매 완료
sdk.track('purchase', {
  product_id: '12345',
  revenue: 29900,
  currency: 'KRW'
});

// 즉시 전송
await sdk.flush();

결제 추적

사용자 결제 시도를 ViaLink 서버(/v1/payments/initiated)로 전송합니다. SDK 1.1.0+ 부터 사용 가능합니다. 고객사 백엔드는 PG 콜백 수신 후 별도로 /v1/payments/succeeded 또는 /failed를 호출해야 합니다 (S2S 인증 + bcrypt 검증).

코드가 복사되었습니다
import { ViaLinkWebSDK } from 'vialink-web-sdk';

ViaLinkWebSDK.init({ apiKey: 'YOUR_API_KEY' });

// 결제창 띄우기 직전:
try {
  const result = await ViaLinkWebSDK.payment.initiated({
    orderId: 'ORD-2026-0001',
    amount: 19900,
    currency: 'KRW',
    paymentMethod: 'card',
    metadata: { productId: 'prod-001' },
  });
  console.log('payment_event_id=', result.paymentEventId);
} catch (e) {
  // 입력 검증 실패 또는 네트워크 오류
  console.error(e);
}

스마트 앱 배너

모바일 웹에서 앱 설치를 유도하는 배너를 표시합니다.

코드가 복사되었습니다
sdk.showBanner({
  title: '앱에서 보기',
  description: '더 빠른 경험을 위해 앱을 사용하세요',
  buttonText: '열기',
  iosStoreUrl: 'https://apps.apple.com/app/id123456',
  androidStoreUrl: 'https://play.google.com/store/apps/...',
  position: 'bottom',
  theme: 'light'
});

// 배너 숨기기
sdk.hideBanner();

React Native SDK

React Native 0.73+, TypeScript 5+ · npm / yarn

설치

npm 또는 yarn으로 설치합니다.

코드가 복사되었습니다
npm install vialink-react-native-sdk
# 또는
yarn add vialink-react-native-sdk

초기화

App.tsx 최상위에서 SDK를 초기화합니다.

코드가 복사되었습니다
import { ViaLinkSDK } from 'vialink-react-native-sdk';

// App.tsx
function App() {
  useEffect(() => {
    ViaLinkSDK.shared.configure('YOUR_API_KEY');

    return () => {
      ViaLinkSDK.shared.destroy();
    };
  }, []);

  return <Navigation />;
}

이벤트 추적

커스텀 이벤트를 추적합니다.

코드가 복사되었습니다
// 구매 완료
ViaLinkSDK.shared.track('purchase', {
  product_id: '12345',
  revenue: 29900,
  currency: 'KRW'
});

// 회원가입
ViaLinkSDK.shared.track('signup');

결제 추적

사용자 결제 시도를 ViaLink 서버(/v1/payments/initiated)로 전송합니다. native bridge로 Android/iOS native SDK의 payment.initiated를 호출합니다 (SDK 2.1.0+). 고객사 백엔드는 PG 콜백 수신 후 별도로 /v1/payments/succeeded 또는 /failed를 호출해야 합니다.

코드가 복사되었습니다
import { ViaLinkSDK } from 'vialink-react-native-sdk';

// 결제창 띄우기 직전:
try {
  const result = await ViaLinkSDK.shared.payment.initiated({
    orderId: 'ORD-2026-0001',
    amount: 19900,
    currency: 'KRW',
    paymentMethod: 'card',
  });
  console.log('payment_event_id=', result.paymentEventId);
} catch (e) {
  // 입력 검증 실패 또는 네트워크 오류
}

플랫폼 설정

iOS와 Android에 각각 딥링크 설정이 필요합니다.

코드가 복사되었습니다
// app.json (Expo)
{
  "expo": {
    "ios": {
      "associatedDomains": ["applinks:vialink.app"]
    },
    "android": {
      "intentFilters": [{
        "action": "VIEW",
        "autoVerify": true,
        "data": [{
          "scheme": "https",
          "host": "vialink.app",
          "pathPrefix": "/{your-slug}/"
        }],
        "category": ["BROWSABLE", "DEFAULT"]
      }]
    }
  }
}

Unity SDK

Unity 2021.3+ LTS, C# 9+ · Unity Package Manager (UPM)

설치

UPM으로 패키지를 추가합니다.

코드가 복사되었습니다
Window > Package Manager > + > Add package from git URL:
https://github.com/aresjoydev/vialink-unity-sdk.git

초기화

첫 씬에 ViaLinkSDK 프리팹을 배치하고 초기화합니다.

코드가 복사되었습니다
using ViaLink;

public class GameManager : MonoBehaviour
{
    void Start()
    {
        ViaLinkSDK.Instance.Initialize("YOUR_API_KEY");
    }
}

이벤트 추적

커스텀 이벤트를 추적합니다.

코드가 복사되었습니다
// 인앱 구매
ViaLinkSDK.Instance.TrackEvent("purchase",
    new Dictionary<string, object>
    {
        { "product_id", "gem_pack_100" },
        { "revenue", 4900 },
        { "currency", "KRW" }
    });

// 레벨 클리어
ViaLinkSDK.Instance.TrackEvent("level_clear",
    new Dictionary<string, object>
    {
        { "level", 10 }
    });

결제 추적

사용자 결제 시도를 ViaLink 서버(/v1/payments/initiated)로 전송합니다. Unity SDK는 콜백 패턴(onSuccess/onError)을 사용합니다 (SDK 1.1.0+). 고객사 백엔드는 PG 콜백 수신 후 별도로 /v1/payments/succeeded 또는 /failed를 호출해야 합니다.

코드가 복사되었습니다
using ViaLink.SDK;

// 결제창 띄우기 직전 (MonoBehaviour 안에서)
ViaLinkSDK.Payment.Initiated(
    new PaymentInitiatedArgs {
        OrderId = "ORD-2026-0001",
        Amount = 19900,
        Currency = "KRW",
        PaymentMethod = "card",
    },
    onSuccess: (result) => {
        Debug.Log($"payment_event_id={result.PaymentEventId}");
    },
    onError: (error) => {
        Debug.LogError($"결제 시도 실패: {error}");
    }
);

Flutter SDK

Flutter 3.19+, Dart 3.11+ · pub.dev

설치

pubspec.yaml에 의존성을 추가합니다.

코드가 복사되었습니다
flutter pub add vialink_flutter_plugin

초기화

앱 시작 시 SDK를 초기화합니다.

코드가 복사되었습니다
import 'package:vialink_flutter_plugin/vialink_flutter_plugin.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await ViaLinkSDK.instance.configure(apiKey: 'YOUR_API_KEY');

  runApp(const MyApp());
}

이벤트 추적

커스텀 이벤트를 추적합니다.

코드가 복사되었습니다
// 구매 완료
ViaLinkSDK.instance.track('purchase', data: {
  'product_id': '12345',
  'revenue': 29900,
  'currency': 'KRW',
});

// 회원가입
ViaLinkSDK.instance.track('signup');

결제 추적

사용자 결제 시도를 ViaLink 서버(/v1/payments/initiated)로 전송합니다. Dart facade가 native plugin(Android/iOS)의 MethodChannel을 통해 native SDK의 payment.initiated를 호출합니다 (SDK 2.1.0+). 고객사 백엔드는 PG 콜백 수신 후 별도로 /v1/payments/succeeded 또는 /failed를 호출해야 합니다.

코드가 복사되었습니다
import 'package:vialink_flutter_plugin/vialink_flutter_plugin.dart';

// 결제창 띄우기 직전:
try {
  final result = await ViaLinkSDK.instance.payment.initiated(
    PaymentInitiatedArgs(
      orderId: 'ORD-2026-0001',
      amount: 19900,
      currency: 'KRW',
      paymentMethod: 'card',
      metadata: {'productId': 'prod-001'},
    ),
  );
  print('payment_event_id=${result.paymentEventId}');
} on ArgumentError catch (e) {
  // 입력 검증 실패
  print(e);
} catch (e) {
  // PlatformException 또는 기타 오류
}

플랫폼 설정

iOS와 Android에 각각 딥링크 설정이 필요합니다.

코드가 복사되었습니다
# iOS: ios/Runner/Runner.entitlements
# Associated Domains 추가:
#   applinks:vialink.app

# Android: android/app/src/main/AndroidManifest.xml
# <intent-filter android:autoVerify="true">
#   <action android:name="android.intent.action.VIEW" />
#   <category android:name="android.intent.category.DEFAULT" />
#   <category android:name="android.intent.category.BROWSABLE" />
#   <data
#     android:scheme="https"
#     android:host="vialink.app"
#     android:pathPrefix="/{your-slug}/" />
# </intent-filter>