시작하기
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
가입과 첫 앱 등록은 어떻게 하나요?
대시보드에 가입한 뒤 앱을 추가하면 즉시 apiKey 와 apiSecret 이 발급됩니다. 이후 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/succeededPOST /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 모두 정상 인식됩니다.
링크 / 도메인
Short Code 를 직접 지정할 수 있나요?
링크 생성 시 customCode 파라미터로 지정할 수 있습니다. 미지정 시 base36 6자리 코드가 자동 생성됩니다.
중복 여부는 POST /api/links/check-short-code 로 미리 확인할 수 있습니다.
한 번 만든 링크를 수정할 수 있나요?
PUT /api/links/:id 또는 대시보드에서 수정 가능합니다. 수정 가능 필드는 다음과 같습니다.
name,description,tagsdeeplinkPath,deeplinkDataiosUrl,androidUrl,webUrlogTitle,ogDescription,ogImageUrlcampaign,channel,featureexpiresAt
단, linkType 자체는 변경할 수 없습니다.
링크 만료 · 비활성 · 삭제는 어떻게 처리되나요?
- 만료 —
expiresAt시점이 지나면 리다이렉트가 차단됩니다 - 비활성 —
suspendedAt으로 일시 정지할 수 있습니다 - 삭제 —
DELETE /api/links/:id호출 시deletedAt이 기록되는 소프트 삭제입니다. 복구가 필요하면 어드민에서 처리할 수 있습니다
커스텀 도메인(예: go.mycompany.com) 을 연결할 수 있나요?
대시보드의 앱 → 도메인 에서 등록합니다. CNAME 타겟을 edge.vialink.app 으로 설정하면 DNS 검증과 SSL 발급이 자동으로 진행됩니다.
도메인 상태는 다음 단계로 표시됩니다.
pending_dns— DNS 검증 대기dns_verified— DNS 검증 완료ssl_provisioning— SSL 인증서 발급 중active— 사용 가능
커스텀 도메인은 customDomain 기능이 포함된 요금제에서 사용할 수 있습니다.
SDK 통합
AppLinks / Universal Links 도메인 인증 파일은 직접 호스팅해야 하나요?
직접 호스팅할 필요가 없습니다. Bridge Server 가 두 파일을 모두 자동 호스팅합니다.
- Android —
GET /.well-known/assetlinks.json. 등록된 패키지명(Play Store · ONE Store) 과 SHA-256 fingerprint 로 동적 생성 - iOS —
GET /.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) 은 대시보드 요금제 페이지에서 확인할 수 있습니다.
분석 데이터는 어디서 보나요?
대시보드의 앱 → 분석 페이지에서 클릭, 설치, 플랫폼별, 캠페인별 통계를 확인할 수 있습니다.
딥링크
앱 정보(Bundle ID 등) 만 입력하고 아직 SDK 를 설치하지 않은 상태에서 테스트 중인데, 왜 특정 플랫폼에서는 앱에 도달하지 않나요?
단순히 "앱을 여는 것" 은 OS 의 영역이지만, 플랫폼에 따라 "이 앱이 실제로 이 링크를 받을 준비가 되었는가" 를 체크하는 로직이 다릅니다.
- 현상 — SDK 가 설치되지 않은 앱은 링크를 통해 앱이 열리더라도, 유저가 클릭한 파라미터(예: 추천인 코드, 특정 상품 번호) 를 해석하지 못해 메인 화면에서 멈추거나 에러가 발생합니다.
- 플랫폼별 차이 — 특히 지연된 딥링크(앱 미설치 유저가 설치 후 진입) 기능을 사용하는 경우, 앱 내부에 SDK 가 없으면 스토어 설치 정보와 링크 클릭 정보를 매칭할 수 없어 흐름이 끊기게 됩니다.
- 해결 — 앱 설정만으로는 절반의 성공입니다. 반드시 앱 내부에 딥링크 SDK 가 탑재되어 시스템의 호출을 받아낼 '안테나' 역할을 해주어야 모든 플랫폼에서 안정적으로 앱 내부 페이지까지 도달할 수 있습니다.
유니버설 링크 설정을 마쳤는데도 인스타그램, 페이스북, 스레드(Threads) 에서 앱이 실행되지 않습니다. 무엇이 문제일까요?
Custom URL Scheme 이 누락되었나요? (SNS 탈출의 열쇠)
인스타그램, 페이스북, 스레드 같은 폐쇄형 SNS 플랫폼은 유니버설 링크를 그대로 수용하지 않고 자체 인앱 브라우저에 유저를 가둡니다.
- 현상 — 유니버설 링크만 설정하고 Custom URL Scheme 설정을 빠뜨리면, 인앱 브라우저에서 "앱으로 이동" 버튼을 눌러도 앱을 호출할 '주소' 가 없어 아무 반응이 일어나지 않습니다.
- 해결 — 앱 설정(
Info.plist) 에 우리 앱만의 고유한 URL Scheme 이 등록되어 있어야 하며, 딥링크 SDK 설정 시에도 이 Scheme 값이 정확히 입력되어야 합니다. 그래야 폐쇄형 플랫폼의 가두리를 뚫고 앱을 열 수 있습니다.
유니버설 링크 설정을 수정했는데 왜 바로 적용되지 않나요?
설정 오류보다는 '캐시(Cache)' 때문일 확률이 매우 높습니다. 유니버설 링크는 보안과 성능을 위해 Apple 이 중간에서 '보증' 을 서주는 구조이며, 이 정보가 업데이트되는 데는 물리적인 시간이 필요합니다.
1. 왜 바로 안 되나요? (3단계 캐시 구조)
유니버설 링크가 작동하기까지 정보는 총 세 단계를 거칩니다. 각 단계마다 정보를 저장하고 있는 기간이 다릅니다.
- 1단계 — Apple CDN 서버 (최대 48시간) — Apple 은 전 세계의 모든 앱 설정을 수시로 검사하지 않습니다. 자체 CDN(데이터 저장소) 에 정보를 담아두고 약 24시간~48시간 주기로 업데이트합니다.
- 2단계 — 사용자 기기 iOS (최대 1주일) — 아이폰의 OS 는 매번 서버에 확인하러 가는 대신, 기기 내부에 정보를 저장해 둡니다. 이 정보는 보통 1주일 단위로 갱신됩니다.
- 3단계 — 플랫폼별 인앱 브라우저 캐시 — 네이버나 페이스북 앱은 자체 브라우저 캐시를 가지고 있어, 이전의 실패했던 연결 정보를 기억하고 있을 수 있습니다.
2. 즉시 테스트하고 싶을 때의 해결책 (개발/QA용)
운영 환경의 캐시가 풀릴 때까지 기다릴 수 없다면 아래 방법을 시도해 보세요.
- 방법 1 — 개발자 모드 활용 (가장 추천) — 아이폰 [설정 > 개발자(Developer) > Associated Domains Development] 를 활성화합니다. 링크 뒤에
?mode=developer파라미터를 붙이면 Apple CDN 을 거치지 않고 서버에서 최신 정보를 직접 가져옵니다. - 방법 2 — 앱 재설치 — 앱을 삭제한 후 다시 설치하면 기기 내 저장된 캐시 정보가 일부 초기화되면서 새 설정을 불러올 수 있습니다.
- 방법 3 — 링크 뒤에 랜덤 파라미터 추가 —
vialink.io/abcd대신vialink.io/abcd?refresh=1처럼 한 번도 사용하지 않은 주소로 접속하면 캐시를 우회하는 경우가 있습니다.
인증
모든 API 호출은 X-API-Key 헤더로 인증합니다. 대시보드에서 앱을 등록하면 API Key가 발급됩니다.
요청 헤더
X-API-Key: <your_api_key>Base URL
모든 API 요청의 기본 URL입니다.
https://vialink.app링크 생성
새로운 딥링크를 생성합니다.
요청 파라미터
linkType 모드 비교
- ·
static(기본값) — 고정 단일 진입점입니다. 발급 URL 뒤에 토큰이 부착되면 404로 처리됩니다 (검색엔진 인덱싱 차단). - ·
dynamic— 발급된 URL 뒤에/사용자값이나?key=value를 붙이면 그 값이 클릭 이력과 SDK 응답에 자동 전달됩니다.
요청 예시
POST /api/links
Content-Type: application/json
X-API-Key: <your_api_key>
{
"linkType": "static",
"deeplinkPath": "/product/12345",
"deeplinkData": { "product_id": "12345" },
"campaign": "summer_sale",
"channel": "email",
"feature": "product_share",
"ogTitle": "여름 세일 특가",
"ogDescription": "지금 확인하세요!",
"ogImageUrl": "https://cdn.example.com/promo.jpg",
"iosUrl": "https://apps.apple.com/app/id123456",
"androidUrl": "https://play.google.com/store/apps/...",
"webUrl": "https://www.example.com/product/12345",
"tags": ["summer", "sale"],
"expiresAt": "2026-12-31T23:59:59Z",
"customCode": "summer-sale-2026",
"name": "2026 봄 시즌 인스타 광고용",
"description": "인스타그램 스토리 광고 전용 링크. 6월 말 만료."
}응답 (201 Created)
{
"id": 1,
"shortCode": "summer-sale-2026",
"shortUrl": "https://vialink.app/{your-slug}/summer-sale-2026",
"deeplinkPath": "/product/12345",
"linkType": "static",
"name": "2026 봄 시즌 인스타 광고용",
"description": "인스타그램 스토리 광고 전용 링크. 6월 말 만료.",
"createdAt": "2026-04-06T12:00:00Z"
}에러 (400 Bad Request)
linkType에 dynamic/static 외의 값을 명시적으로 보냈을 때 (필드를 생략하면 자동으로 static으로 저장됩니다):
{
"error": "linkType은 'dynamic' 또는 'static'이어야 합니다."
}에러 (409 Conflict)
customCode가 이미 동일 앱에서 사용 중일 때 (대소문자 구분 없이 검사):
{
"error": "이미 사용 중인 short code 입니다."
}링크 조회
특정 링크의 전체 정보를 조회합니다.
경로 파라미터
응답 (200 OK)
{
"id": 1,
"shortCode": "aB3xK",
"shortUrl": "https://vialink.app/{your-slug}/aB3xK",
"linkType": "static",
"deeplinkPath": "/product/12345",
"deeplinkData": { "product_id": "12345" },
"campaign": "summer_sale",
"channel": "email",
"feature": "product_share",
"ogTitle": "여름 세일 특가",
"ogDescription": "지금 확인하세요!",
"ogImageUrl": "https://cdn.example.com/promo.jpg",
"iosUrl": "https://apps.apple.com/app/id123456",
"androidUrl": "https://play.google.com/store/apps/...",
"webUrl": "https://www.example.com/product/12345",
"expiresAt": "2026-12-31T23:59:59Z",
"name": "2026 봄 시즌 인스타 광고용",
"description": "인스타그램 스토리 광고 전용 링크. 6월 말 만료.",
"createdAt": "2026-04-06T12:00:00Z",
"updatedAt": "2026-04-06T12:00:00Z"
}링크 수정
기존 링크의 정보를 수정합니다. 변경할 필드만 전달합니다.
요청 파라미터
linkType은 변경 불가
본 엔드포인트의 요청 본문에 linkType 키가 포함되면 400으로 거부됩니다. 모드를 바꾸려면 새 링크를 생성해 주세요.
요청 예시
PUT /api/links/1
Content-Type: application/json
X-API-Key: <your_api_key>
{
"ogTitle": "업데이트된 제목",
"webUrl": "https://www.example.com/new-page",
"name": "2026 여름 시즌 광고용",
"description": "여름 캠페인으로 업데이트."
}응답 (200 OK)
수정된 전체 링크 객체가 반환됩니다 (name, description 포함).
에러 (400 Bad Request)
요청 본문에 linkType 키가 포함된 경우:
{
"error": "모드는 변경할 수 없습니다. 새 링크를 생성해 주세요."
}링크 삭제
링크를 삭제합니다 (소프트 삭제). 삭제된 링크의 URL은 더 이상 작동하지 않습니다.
경로 파라미터
응답 (200 OK)
{
"success": true
}링크 목록
생성된 링크 목록을 조회합니다.
API Key로 인증된 앱의 링크 목록을 반환합니다.
응답 (200 OK)
{
"links": [
{
"id": 1,
"shortCode": "summer-sale-2026",
"shortUrl": "https://vialink.app/{your-slug}/summer-sale-2026",
"linkType": "static",
"deeplinkPath": "/product/12345",
"name": "2026 봄 시즌 인스타 광고용",
"description": "인스타그램 스토리 광고 전용 링크. 6월 말 만료.",
"createdAt": "2026-04-06T12:00:00Z"
}
],
"total": 50
}Short Code 중복 확인
링크 생성 전 customCode가 사용 가능한지 실시간으로 확인합니다. 인증: 세션 쿠키 + 해당 테넌트의 ADMIN 이상 권한 필요.
쿼리 파라미터
요청 예시
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\}/PTMLWT와vialink.app/\{slug\}/ptmlwt는 동일 링크를 매칭.
결제 시도 (SDK)
사용자가 결제창을 띄우는 시점에 SDK가 호출하는 엔드포인트입니다. 같은 order_id로 여러 번 호출되어도 새 row가 생성됩니다 (사용자 카드 오입력 → 재시도 케이스 추적). 매칭은 이 단계에서 수행하지 않고, 이후 /v1/payments/succeeded가 호출될 때 동기로 매칭됩니다.
요청 헤더
X-Api-Key: <your_api_key>
Content-Type: application/json요청 파라미터
요청 예시
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"
}에러 응답
결제 성공 (S2S)
고객사 백엔드가 PG 콜백을 수신한 뒤 ViaLink 서버를 호출하는 Server-to-Server 엔드포인트입니다. 매칭 파이프라인이 동기로 실행되어 응답에 attributed 객체가 포함됩니다. SDK에서 직접 호출하지 마세요 (apiSecret이 노출됩니다).
요청 헤더
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/jsonapiSecret은 DB에 bcrypt hash로 저장되며 평문 비교가 아닌 bcrypt.compare로 검증됩니다. 인증 실패는 단일 메시지 "인증에 실패했습니다."로 통합되어 (apiKey 존재 여부 / apiSecret 일치 여부 / 테넌트 활성 상태) 어느 단계에서 실패했는지 노출되지 않습니다.
요청 파라미터
요청 예시
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가지 값입니다.
에러 응답
결제 실패 (S2S)
PG 결제 실패 webhook을 받은 뒤 고객사 백엔드가 호출합니다. 퍼널 분석을 위해 매칭은 동일하게 수행되어 DB에 저장되지만 응답에는 attributed 객체를 노출하지 않습니다 (실패 케이스에서 클릭/설치 매칭 결과를 클라이언트에 전달할 의미가 없기 때문).
요청 헤더
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/json요청 파라미터
요청 예시
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_id로 failed가 다시 호출되면 멱등 처리됩니다 (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"
}에러 응답
어트리뷰션 정책
결제 성공/실패가 어떤 클릭/설치에 귀속되는지 결정하는 규칙입니다. 마지막-클릭(last_click) 모델을 따르며, /v1/payments/succeeded 또는 /v1/payments/failed가 호출되는 시점에 동기로 매칭이 수행됩니다(eager). 매칭 결과는 payment_events 테이블의 attributed_* 컬럼에 즉시 기록됩니다.
윈도우 설정
어트리뷰션 윈도우는 SystemConfig에 저장되며 어드민 system-config 메뉴에서 조정할 수 있습니다.
매칭 단계 (의사코드)
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"매칭 실패 케이스
설치
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")
}
}딥링크 처리
딥링크 콜백을 등록하고, Activity에서 진입 Intent를 처리합니다. SDK 3.1+: 일반 진입(아이콘 탭 등)에서도 콜백이 호출됩니다(data == null).
// 딥링크 콜백 등록 (SDK 3.1+: (DeepLinkData?) -> Unit)
// handleIntent 호출 시 항상 1회 호출:
// - data != null : App Link 진입 → data.path로 라우팅
// - data == null : 일반 진입(아이콘 탭 등) → 기본 화면 유지
ViaLinkSDK.onDeepLink { data ->
if (data == null) {
Log.d("ViaLink", "일반 진입 — 기본 화면 유지")
return@onDeepLink
}
Log.d("ViaLink", "경로: ${data.path}")
Log.d("ViaLink", "파라미터: ${data.params}")
}
// 디퍼드 딥링크 콜백 (SDK 3.2+: 앱 진입 시 매번 1회 호출)
// - 첫 실행: 5초 안에 매칭 결과 결정 즉시 호출 (timeout/실패 시 error)
// - 비첫 실행: 즉시 (null, null) — /v1/open 호출 X (organic과 동일 의미)
ViaLinkSDK.onDeferredDeepLink { data, error ->
when {
error != null -> Log.w("ViaLink", "디퍼드 매칭 실패: ${error.code}")
data == null -> Log.d("ViaLink", "organic 또는 비첫 실행 — 일반 진입")
else -> Log.d("ViaLink", "디퍼드: ${data.path}")
}
}
// Activity에서 진입 Intent 처리 (SDK 3.1+: 일반 진입에서도 콜백 호출)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ViaLinkSDK.handleIntent(intent) // 항상 호출
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
ViaLinkSDK.handleIntent(intent)
}
}이벤트 추적
커스텀 이벤트를 추적합니다. 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) {
// 네트워크 오류
}
}링크 생성
앱 내에서 딥링크를 생성하여 공유할 수 있습니다. 폴백 URL/OG 메타태그/채널·태그·만료일 등 서버가 받는 모든 옵션을 named argument로 전달할 수 있습니다. linkType 기본값은 "static"(고정 단일 진입, 추적 X) — 클릭 추적이 필요하면 "dynamic"을 명시합니다.
lifecycleScope.launch {
val result = ViaLinkSDK.createLink(
path = "/product/12345",
data = mapOf("promo_code" to "FRIEND_SHARE"),
campaign = "referral",
linkType = "dynamic", // 클릭 추적 필요 (생략 시 "static")
iosUrl = "https://apps.apple.com/app/id123456",
androidUrl = "https://play.google.com/store/apps/details?id=com.example",
webUrl = "https://www.example.com/product/12345",
ogTitle = "여름 세일 특가",
ogDescription = "지금 확인하세요!",
ogImageUrl = "https://cdn.example.com/promo.jpg",
channel = "email",
feature = "product_share",
tags = listOf("summer", "sale"),
expiresAt = "2026-12-31T23:59:59Z"
)
result.onSuccess { shortUrl ->
// shortUrl: "https://vialink.app/{your-slug}/xYz12"
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, shortUrl)
type = "text/plain"
}
startActivity(Intent.createChooser(sendIntent, "공유하기"))
}
}링크 생성 (Java에서 호출)
앱 소스가 Java면 suspend fun 직접 호출이 불가능합니다. SDK 1.4.0+ 부터 Callback/CompletableFuture wrapper를, 1.5.0+ 부터 풀 옵션은 CreateLinkOptions Builder로 전달합니다.
import com.vialink.sdk.ViaLinkSDK;
import com.vialink.sdk.model.CreateLinkOptions;
import java.util.Map;
import java.util.List;
// 풀 옵션 — CreateLinkOptions Builder 사용 (Callback)
CreateLinkOptions opts = new CreateLinkOptions.Builder()
.data(Map.of("promo_code", "FRIEND_SHARE"))
.campaign("referral")
.linkType("dynamic")
.iosUrl("https://apps.apple.com/app/id123456")
.androidUrl("https://play.google.com/store/apps/details?id=com.example")
.webUrl("https://www.example.com/product/12345")
.ogTitle("여름 세일 특가")
.ogImageUrl("https://cdn.example.com/promo.jpg")
.channel("email")
.feature("product_share")
.tags(List.of("summer", "sale"))
.expiresAt("2026-12-31T23:59:59Z")
.build();
ViaLinkSDK.createLinkAsync(
"/product/12345",
opts,
shortUrl -> { /* 성공 (메인 스레드) */ },
err -> Log.e("ViaLink", "링크 생성 실패", err)
);
// CompletableFuture 스타일 (minSdk 24+)
ViaLinkSDK.createLinkFuture("/product/12345", opts)
.thenAccept(shortUrl -> { /* 성공 */ })
.exceptionally(err -> { /* 실패 */ return null; });
// 단순 호출 — 폴백/OG 등 옵션이 필요 없을 때
ViaLinkSDK.createLinkAsync(
"/product/12345",
Map.of("promo_code", "FRIEND_SHARE"),
"referral",
"dynamic",
shortUrl -> {},
err -> {}
);App Links 설정
AndroidManifest.xml에 App Links Intent Filter를 추가합니다.
<activity
android:name=".MainActivity"
android:exported="true">
<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>
</activity>설치
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)
}
}
}
}딥링크 처리
딥링크 콜백을 등록하고, SceneDelegate에서 Universal Link를 처리합니다. SDK 3.1+: 일반 진입(아이콘 탭 등)에서도 콜백이 호출됩니다(data == nil).
// 딥링크 콜백 등록 (SDK 3.1+: (DeepLinkData?) -> Void)
// 진입 알림 시 항상 1회 호출:
// - data != nil : Universal Link 진입 → data.path로 라우팅
// - data == nil : 일반 진입(아이콘 탭 등) → 기본 화면 유지
ViaLinkSDK.shared.onDeepLink { data in
guard let data = data else {
print("일반 진입 — 기본 화면 유지")
return
}
print("경로: \(data.path)")
print("파라미터: \(data.params)")
}
// 디퍼드 딥링크 콜백 (SDK 3.2+: 앱 진입 시 매번 1회 호출)
// - 첫 실행: 5초 안에 매칭 결과 결정 즉시 호출 (timeout/실패 시 error)
// - 비첫 실행: 즉시 (nil, nil) — /v1/open 호출 X (organic과 동일 의미)
ViaLinkSDK.shared.onDeferredDeepLink { data, error in
if let error = error {
print("디퍼드 매칭 실패: \(error.code.rawValue)")
} else if let data = data {
print("디퍼드: \(data.path)")
} else {
print("organic 또는 비첫 실행 — 일반 진입")
}
}
// SceneDelegate에서 Universal Link 처리 + 일반 진입 알림 (3.1+)
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options: UIScene.ConnectionOptions) {
if let activity = options.userActivities.first {
ViaLinkSDK.shared.handleUniversalLink(activity)
} else if let urlContext = options.urlContexts.first {
ViaLinkSDK.shared.handleURL(urlContext.url)
} else {
ViaLinkSDK.shared.notifyAppLaunch() // 일반 진입
}
}
func scene(_ scene: UIScene,
continue userActivity: NSUserActivity) {
ViaLinkSDK.shared.handleUniversalLink(userActivity)
}이벤트 추적
커스텀 이벤트를 추적합니다.
// 구매 완료
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 {
// 기타 오류
}
}링크 생성
앱 내에서 딥링크를 생성합니다. 폴백 URL/OG 메타태그/채널·태그·만료일 등 서버가 받는 모든 옵션을 named argument로 전달할 수 있습니다.
let shortUrl = try await ViaLinkSDK.shared.createLink(
path: "/product/12345",
data: ["promo_code": "FRIEND_SHARE"],
campaign: "referral",
linkType: "dynamic", // 클릭 추적 필요 (생략 시 "static")
iosUrl: "https://apps.apple.com/app/id123456",
androidUrl: "https://play.google.com/store/apps/details?id=com.example",
webUrl: "https://www.example.com/product/12345",
ogTitle: "여름 세일 특가",
ogDescription: "지금 확인하세요!",
ogImageUrl: "https://cdn.example.com/promo.jpg",
channel: "email",
feature: "product_share",
tags: ["summer", "sale"],
expiresAt: "2026-12-31T23:59:59Z"
)
// shortUrl: "https://vialink.app/{your-slug}/xYz12"
let activityVC = UIActivityViewController(
activityItems: [shortUrl],
applicationActivities: nil
)
present(activityVC, animated: true)Universal Link 설정
Xcode에서 Associated Domains를 설정합니다.
1. Xcode > Target > Signing & Capabilities
2. + Capability > Associated Domains
3. 도메인 추가: applinks:vialink.app설치
npm 또는 CDN으로 설치합니다.
npm install vialink-web-sdk초기화
SDK를 초기화합니다. 페이지 이탈 시에도 이벤트가 전송됩니다.
import { ViaLinkWebSDK } from 'vialink-web-sdk';
const sdk = ViaLinkWebSDK.init({
apiKey: 'YOUR_API_KEY'
});딥링크 처리
딥링크 콜백을 등록하거나 현재 URL에서 직접 추출합니다. SDK 3.1+: onDeepLink 등록 즉시 1회 콜백 호출(페이지 로드 = 진입).
// 콜백 패턴 (SDK 3.1+: 등록 즉시 현재 URL 1회 파싱)
// - data != null : ViaLink 딥링크 형식 진입 → data.path로 라우팅
// - data == null : 일반 진입(랜딩 페이지 등) → 기본 라우트 유지
sdk.onDeepLink((data) => {
if (!data) {
console.log('일반 진입 — 기본 라우트 유지');
return;
}
console.log('경로:', data.path);
console.log('파라미터:', data.params);
// window.location.href = data.path 또는 라우터 navigate
});
// 동기 추출 패턴 (페이지 로드 시점에 즉시)
const data = sdk.getDeepLinkData();
if (data) {
console.log('shortCode:', data.shortCode);
}
// 정적 메서드로 임의 URL 파싱
const parsed = ViaLinkWebSDK.parseDeepLinkFromURL(
'https://vialink.app/{your-slug}/xYz12?ref=share'
);이벤트 추적
커스텀 이벤트를 추적합니다. 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);
}링크 생성
웹에서 딥링크를 생성합니다. 폴백 URL/OG 메타태그/채널·태그·만료일 등 부가 옵션은 5번째 인자(객체)로 전달합니다.
const shortUrl = await sdk.createLink(
'/product/12345',
{ promo_code: 'FRIEND_SHARE' },
'referral',
'dynamic', // 클릭 추적 필요 (생략 시 'static')
{
iosUrl: 'https://apps.apple.com/app/id123456',
androidUrl: 'https://play.google.com/store/apps/details?id=com.example',
webUrl: 'https://www.example.com/product/12345',
ogTitle: '여름 세일 특가',
ogDescription: '지금 확인하세요!',
ogImageUrl: 'https://cdn.example.com/promo.jpg',
channel: 'email',
feature: 'product_share',
tags: ['summer', 'sale'],
expiresAt: '2026-12-31T23:59:59Z',
}
);
console.log(shortUrl); // "https://vialink.app/{your-slug}/xYz12"설치
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 />;
}딥링크 처리
딥링크 콜백을 등록합니다. SDK 3.1+: 일반 진입(아이콘 탭 등)에서도 콜백이 호출됩니다(data === null).
// 딥링크 콜백 등록 (SDK 3.1+: (data: DeepLinkData | null) => void)
// 네이티브 진입 알림 시 항상 1회 호출:
// - data != null : App Links / Universal Links 진입 → data.path로 라우팅
// - data == null : 일반 진입(아이콘 탭 등) → 기본 라우트 유지
ViaLinkSDK.shared.onDeepLink((data) => {
if (!data) {
console.log('일반 진입 — 기본 라우트 유지');
return;
}
console.log('경로:', data.path);
console.log('파라미터:', data.params);
navigation.navigate(data.path, data.params);
});
// 디퍼드 딥링크 콜백 (SDK 3.2+: 앱 진입 시 매번 1회 호출)
// - 첫 실행: /v1/open 매칭 결과를 5초 안에 전달 (timeout/실패 시 error)
// - 비첫 실행: 즉시 (null, null) — 네트워크 호출 X (organic과 동일 의미)
ViaLinkSDK.shared.onDeferredDeepLink((data, error) => {
if (error) {
console.warn('디퍼드 매칭 실패:', error.code);
return;
}
if (!data) {
console.log('organic 또는 비첫 실행 — 일반 진입');
return;
}
navigation.navigate(data.path, data.params);
});이벤트 추적
커스텀 이벤트를 추적합니다.
// 구매 완료
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) {
// 입력 검증 실패 또는 네트워크 오류
}링크 생성
앱 내에서 딥링크를 생성하여 공유합니다. 폴백 URL/OG/채널 등 부가 옵션은 5번째 인자(객체)로 전달합니다.
const shortUrl = await ViaLinkSDK.shared.createLink(
'/product/12345',
{ promo_code: 'FRIEND_SHARE' },
'referral',
'dynamic', // 클릭 추적 필요 (생략 시 'static')
{
iosUrl: 'https://apps.apple.com/app/id123456',
androidUrl: 'https://play.google.com/store/apps/details?id=com.example',
webUrl: 'https://www.example.com/product/12345',
ogTitle: '여름 세일 특가',
ogImageUrl: 'https://cdn.example.com/promo.jpg',
channel: 'sms',
feature: 'product_share',
tags: ['summer', 'sale'],
expiresAt: '2026-12-31T23:59:59Z',
}
);
await Share.share({ message: shortUrl });플랫폼 설정
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"]
}]
}
}
}설치
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");
}
}딥링크 처리
이벤트를 구독하여 딥링크를 처리합니다. SDK 3.1+: 일반 진입(아이콘 탭 등)에서도 이벤트가 발생합니다(data == null).
// 딥링크 콜백 (SDK 3.1+: 일반 진입에서도 호출 — data == null로 전달)
ViaLinkSDK.Instance.OnDeepLink += (data) =>
{
if (data == null)
{
Debug.Log("일반 진입 — 기본 화면 유지");
return;
}
Debug.Log($"경로: {data.Path}");
Debug.Log($"파라미터: {data.Params}");
SceneManager.LoadScene(data.Path);
};
// 디퍼드 딥링크 콜백 (SDK 3.2+: 앱 진입 시 매번 1회 발생)
// - 첫 실행: /v1/open 매칭 결과를 5초 안에 전달 (timeout/실패 시 error)
// - 비첫 실행: 즉시 (null, null) — 네트워크 호출 X (organic과 동일 의미)
ViaLinkSDK.Instance.OnDeferredDeepLink += (data, error) =>
{
if (error != null)
{
Debug.LogWarning($"디퍼드 매칭 실패: {error.Code}");
return;
}
if (data == null)
{
Debug.Log("organic 또는 비첫 실행 — 일반 진입");
return;
}
Debug.Log($"디퍼드 딥링크: {data.Path}");
SceneManager.LoadScene(data.Path);
};이벤트 추적
커스텀 이벤트를 추적합니다.
// 인앱 구매
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}");
}
);링크 생성
게임 내에서 초대 링크를 생성합니다. 폴백 URL/OG 메타태그/채널·태그·만료일 등 서버가 받는 모든 옵션을 named argument로 전달할 수 있습니다.
ViaLinkSDK.Instance.CreateLink(
path: "/invite",
data: new Dictionary<string, object>
{
{ "inviter_id", PlayerId },
{ "reward", "gem_50" }
},
campaign: "friend_invite",
linkType: "dynamic", // 클릭 추적 필요 (생략 시 "static")
iosUrl: "https://apps.apple.com/app/id123456",
androidUrl: "https://play.google.com/store/apps/details?id=com.example",
webUrl: "https://www.example.com/invite",
ogTitle: "친구 초대 — 보상 받기",
ogImageUrl: "https://cdn.example.com/invite.jpg",
channel: "in_game",
feature: "friend_invite",
tags: new List<string> { "invite", "viral" },
expiresAt: "2026-12-31T23:59:59Z",
onSuccess: (shortUrl) =>
{
// shortUrl: "https://vialink.app/{your-slug}/xYz12"
GUIUtility.systemCopyBuffer = shortUrl;
ShowToast("초대 링크가 복사되었습니다!");
},
onError: (error) =>
{
Debug.LogError($"링크 생성 실패: {error}");
}
);설치
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());
}딥링크 처리
SDK가 딥링크를 자동 수신합니다. 콜백만 등록하면 됩니다. SDK 3.1+: 일반 진입(아이콘 탭 등)에서도 콜백이 호출됩니다(data == null).
// 딥링크 콜백 (SDK 3.1+: (DeepLinkData?) -> void)
// 진입 알림 시 항상 1회 호출:
// - data != null : App Links / Universal Links 진입 → data.path로 라우팅
// - data == null : 일반 진입(아이콘 탭 등) → 기본 라우트 유지
ViaLinkSDK.instance.onDeepLink((data) {
if (data == null) {
print('일반 진입 — 기본 라우트 유지');
return;
}
print('경로: ${data.path}');
print('파라미터: ${data.params}');
Navigator.pushNamed(context, data.path,
arguments: data.params);
});
// 디퍼드 딥링크 콜백 (SDK 3.2+: 앱 진입 시 매번 1회 호출)
// - 첫 실행: /v1/open 매칭 결과를 5초 안에 전달 (timeout/실패 시 error)
// - 비첫 실행: 즉시 (null, null) — 네트워크 호출 X (organic과 동일 의미)
ViaLinkSDK.instance.onDeferredDeepLink((data, error) {
if (error != null) {
debugPrint('디퍼드 매칭 실패: ${error.code}');
return;
}
if (data == null) {
debugPrint('organic 또는 비첫 실행 — 일반 진입');
return;
}
Navigator.pushNamed(context, data.path, arguments: data.params);
});이벤트 추적
커스텀 이벤트를 추적합니다.
// 구매 완료
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 또는 기타 오류
}링크 생성
앱 내에서 딥링크를 생성합니다. 폴백 URL/OG 메타태그/채널·태그·만료일 등 서버가 받는 모든 옵션을 named argument로 전달할 수 있습니다.
final shortUrl = await ViaLinkSDK.instance.createLink(
path: '/product/12345',
data: {'promo_code': 'FRIEND_SHARE'},
campaign: 'referral',
linkType: 'dynamic', // 클릭 추적 필요 (생략 시 'static')
iosUrl: 'https://apps.apple.com/app/id123456',
androidUrl: 'https://play.google.com/store/apps/details?id=com.example',
webUrl: 'https://www.example.com/product/12345',
ogTitle: '여름 세일 특가',
ogDescription: '지금 확인하세요!',
ogImageUrl: 'https://cdn.example.com/promo.jpg',
channel: 'email',
feature: 'product_share',
tags: ['summer', 'sale'],
expiresAt: '2026-12-31T23:59:59Z',
);
// shortUrl: "https://vialink.app/{your-slug}/xYz12"
await Share.share(shortUrl);플랫폼 설정
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>