ViaLink
/

Getting Started

New to ViaLink? Start with the questions below. For more detailed behavior, see the REST API and SDK guides below.

What kind of service is ViaLink?

ViaLink is deep link / attribution infrastructure that turns every click from your app, web, and ad campaigns into a single short link, and tracks where that link was clicked and which user went on to install and pay within the app.

In one place, it provides automatic hosting of the domain verification files for Android App Links and iOS Universal Links, deferred deep linking for users who don't have the app installed, and attribution across the full click → install → payment flow.

How is this different from existing short URL services (e.g., bit.ly)?

A URL shortener only decides "where to send" the click. ViaLink additionally automates the following.

  • OS / browser branching (iOS · Android · Web)
  • Routing users who have the app installed to the app, and users who don't to the app store
  • Deferred deep linking that sends users to the originally intended screen on first launch after install
  • Attribution that matches click · install · payment and aggregates it by ad channel / campaign
Which platforms are supported?

We provide 6 official SDKs. All features are also available through the REST API alone.

  • Android — Gradle coordinate com.vialink:sdk
  • iOS — SPM product ViaLinkCore
  • Web — npm vialink-web-sdk
  • React Native — npm vialink-react-native-sdk
  • Unity — UPM (installed via GitHub URL)
  • Flutter — pub.dev vialink_flutter_plugin
How do I sign up and register my first app?

After you sign up on the dashboard and add an app, an apiKey and apiSecret are issued immediately. From there, you can start integration right away by putting the keys into the initialization code in the SDK guide.

API Key / Authentication

Where do I get an API Key?

You can find it under the App → SDK tab in the dashboard. It's issued automatically when you create an app, so there's no separate application process.

What's the difference between the API Key and the API Secret?

The API Key is a public identifier used in the SDK and clients, while the API Secret is a private key used only for server-to-server (S2S) calls. Never include the Secret in client-side (app/web) code.

This is also reflected in how they're stored: the Key is stored in plaintext (UUID), while the Secret is stored as a bcrypt hash, so not even the server can view the original value.

Which endpoints require the Secret?

Only the following two S2S endpoints require the Secret.

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

All other SDK calls (/v1/resolve, /v1/deferred-match, /v1/events, /v1/payments/initiated, etc.) work with just X-API-Key.

I've exposed a key. How do I reissue it?

You can reissue it immediately from the SDK tab in the dashboard using the Reissue API Secret button.

Note: There is no grace period. The existing Secret is invalidated the moment you reissue, so if you have a server in production, deploy the new Secret first, then reissue.

Is the API Key per-account or per-app?

It's per-app (tenant). One account can create multiple apps, and each app is issued its own separate Key / Secret.

Does the case of the authentication header name matter?

HTTP headers are case-insensitive. Both X-API-Key and x-api-key are recognized correctly.

SDK Integration

Do I need to host the App Links / Universal Links domain verification files myself?

No, you don't need to host them yourself. Bridge Server automatically hosts both files.

  • AndroidGET /.well-known/assetlinks.json. Generated dynamically from the registered package name (Play Store · ONE Store) and SHA-256 fingerprint
  • iOSGET /.well-known/apple-app-site-association. Generated dynamically from the Bundle ID · Team ID · slug

Just register the Bundle ID / SHA-256 in the app settings in the dashboard, and it's reflected automatically.

What's the difference between /v1/resolve and /v1/deferred-match?
  • POST /v1/resolve — When the app is opened directly via App Links / Universal Links, immediately looks up link data by short code (Redis cache first)
  • POST /v1/deferred-match — On the app's first launch, matches a prior click by fingerprint (deferred deep linking)

If you follow the standard flow in the SDK integration guide, both calls are handled automatically.

How do I record events?
  • POST /v1/events — A single event
  • POST /v1/events/batch — Batched events, up to 100 per request (useful for offline queuing)

The server doesn't separately validate event names against a fixed standard. You can freely define names like signup, purchase, app.open, or whatever you need.

Payments / Attribution

How does the payment attribution flow work?
  • The SDK sends the payment attempt via POST /v1/payments/initiated (X-API-Key)
  • Your backend, upon receiving the PG (payment gateway) callback, calls POST /v1/payments/succeeded or /failed (X-API-Key + X-API-Secret)
  • Matching happens synchronously at the time of the terminal call — the result is recorded immediately in the payment_events table's attributed_* columns

Since this isn't an asynchronous batch job, you can see the attribution result right away in the call's response.

What attribution model is used?

We use the last-click (last_click) model. The most recent click from the same device is the one credited with attribution.

What are the default attribution windows?
  • Click → install matching: 7 days (attribution.click_to_install_days)
  • Install → payment matching: 30 days (attribution.install_to_purchase_days)
  • initiated → succeeded/failed matching: 24 hours (attribution.initiated_to_succeeded_hours)

All three values can be adjusted in system settings in the admin panel. See the Attribution Policy section below for detailed pseudocode.

What happens if matching fails?

The call still responds with 200, but attributed.method is recorded as one of the following values.

  • no_initiated_or_no_device — No initiated call, or device_id is missing
  • no_install_in_window — No app.install event from the same device within the window
  • organic_install — An install was captured, but it's an organic install with no link_id
  • last_click_install_only — The install was matched, but there is no click event
  • error — DB lookup failed (a Telegram alert is sent)

See the pseudocode in the Attribution Policy section for what each case means and the matching steps.

Plans / Operations

What plans are available?

There are 6 plans.

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

You can check each plan's monthly credit, API call count, app count, and deep link count limits, as well as the available features (customDomain, customLanding, teamInvite, paymentTracking, customEvent), on the Plans page in the dashboard.

Where can I see analytics data?

You can check click, install, per-platform, and per-campaign statistics on the App → Analytics page in the dashboard.

Authentication

All API calls are authenticated with the X-API-Key header. Registering an app in the dashboard issues an API Key.

Request Headers

Code copied to clipboard
X-API-Key: <your_api_key>

Base URL

The base URL for all API requests.

Code copied to clipboard
https://vialink.app

Check Short Code

GET/api/links/check-short-code

Checks in real time whether customCode is available before creating a link. Auth: session cookie + ADMIN or higher role for the tenant required.

Query Parameters

tenantIdstringRequired
App (tenant) ID
codestringRequired
The short code string to check for duplicates

Request Example

Code copied to clipboard
GET /api/links/check-short-code?tenantId=tenant_abc&code=summer-sale-2026

Response (200 OK — available)

Code copied to clipboard
{
  "available": true
}

Response (200 OK — already in use)

Code copied to clipboard
{
  "available": false,
  "reason": "This short code is already in use."
}

Short Code Policy

  • · Auto-generated: Base36 lowercase (a–z, 0–9), 6 chars. (Changed from the former mixed-case Base62.)
  • · Custom: /^[a-z0-9_-]{3,24}$/ — lowercase letters, digits, hyphens, underscores, 3–24 chars.
  • · Case-insensitive routing: vialink.app/\{slug\}/PTMLWT and vialink.app/\{slug\}/ptmlwt match the same link.

Payment Initiated (SDK)

POST/v1/payments/initiated

The endpoint the SDK calls when the user opens the payment sheet. Even if called multiple times with the same order_id, a new row is created (to track retry cases such as mistyped card details). Matching is not performed at this stage; it runs synchronously later when /v1/payments/succeeded is called.

Request Headers

Code copied to clipboard
X-Api-Key: <your_api_key>
Content-Type: application/json

Request Parameters

order_idstringRequired
Order identifier. 1–100 chars, letters/digits/hyphens/underscores allowed (^[A-Za-z0-9_-]{1,100}$).
amountnumberRequired
Payment amount. A number greater than 0. In the currency's own unit (e.g. KRW = won, USD = dollars).
currencystringRequired
ISO 4217 currency code (uppercase). Only values registered in SystemConfig payments.allowed_currencies are allowed.
link_idnumberOptional
ID of the link directly associated with the payment. Attribution is performed separately via device_id-based install/click tracking.
payment_methodstringOptional
Payment method (card / transfer / kakaopay, etc.). Stored up to 50 chars.
device_infoobjectOptional
Device information. Collected automatically by the SDK (device_id, country, etc.). Used for attribution matching.
metadataobjectOptional
Free-form additional data. Stored as a JSON object as-is.

Request Example

Code copied to clipboard
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" }
}

Response (200 OK)

Code copied to clipboard
{
  "success": true,
  "payment_event_id": "9876543210"
}

Error Responses

400Bad RequestOptional
Invalid input format. e.g. { "error": "order_id가 올바르지 않습니다 (1~100자, 영문/숫자/하이픈/언더스코어)." }
401UnauthorizedOptional
Missing header. { "error": "API Key가 필요합니다." }
403ForbiddenOptional
Invalid API Key or inactive tenant. { "error": "유효하지 않은 API Key입니다." }
500Server ErrorOptional
Internal server error. { "error": "서버 오류가 발생했습니다." }

Payment Succeeded (S2S)

POST/v1/payments/succeeded

A Server-to-Server endpoint the customer backend calls after receiving the PG callback. The matching pipeline runs synchronously, so the response includes an attributed object. Do not call it directly from the SDK (it would expose apiSecret).

Request Headers

Code copied to clipboard
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/json

apiSecret is stored as a bcrypt hash in the DB and verified with bcrypt.compare rather than plaintext comparison. Authentication failures are unified into the single message "인증에 실패했습니다." so it is not revealed at which stage it failed (apiKey existence / apiSecret match / tenant active status).

Request Parameters

order_idstringRequired
The same order identifier as the initiated stage. 1–100 chars, letters/digits/hyphens/underscores.
amountnumberRequired
Actual payment amount. A number greater than 0.
currencystringRequired
ISO 4217 currency code (uppercase).
transaction_idstringOptional
PG transaction number. Stored up to 200 chars.
payment_methodstringOptional
Payment method. Stored up to 50 chars.
metadataobjectOptional
Free-form additional data. JSON object.

Request Example

Code copied to clipboard
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" }
  }'

Response (200 OK, normal)

Code copied to clipboard
{
  "success": true,
  "payment_event_id": "9876543220",
  "attributed": {
    "matched": true,
    "method": "last_click",
    "link_id": 12345,
    "click_event_id": 678901,
    "install_event_id": "5544332211"
  }
}

Response (200 OK, idempotent — re-call with the same status)

Code copied to clipboard
{
  "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"
  }
}

Response (409 Conflict — different terminal status)

Occurs when the same order_id already exists in a different terminal state (e.g. failed, refunded, canceled).

Code copied to clipboard
{
  "error": "payment already in terminal state",
  "existing_status": "failed"
}

attribution_method values

The 7 possible values of attributed.method.

last_clickmatchedOptional
Both installed → click were matched (the most normal case). link_id, click_event_id, and install_event_id are all populated.
last_click_install_onlymatchedOptional
The install was matched but no click was within the window (e.g. a pre-installed app activated by push). link_id and install_event_id are populated; click_event_id is null.
organic_installunmatchedOptional
There is an install but link_id is null (organic install). Only install_event_id is populated.
no_install_in_windowunmatchedOptional
No install event within the install_to_purchase_days window for the device_id from the initiated stage.
no_initiated_or_no_deviceunmatchedOptional
There is no initiated row, or device_id is empty, so matching cannot even be attempted.
errorunmatchedOptional
Internal error in the matching pipeline such as a DB query failure. The response still returns 200 normally, and the error is logged separately.
unmatchedunmatchedOptional
Default/reserved. In practice the response always falls into one of the 6 branches above.

Error Responses

400Bad RequestOptional
Invalid input format (order_id pattern, amount range, disallowed currency, etc.).
401UnauthorizedOptional
Missing X-Api-Key or X-Api-Secret header. { "error": "API Key가 필요합니다." } or { "error": "API Secret이 필요합니다." }
403ForbiddenOptional
Authentication failure (apiKey/apiSecret mismatch, inactive tenant). { "error": "인증에 실패했습니다." } — the detailed reason is logged only on the server.
409ConflictOptional
A different terminal status already exists. The existing_status field indicates which status.
500Server ErrorOptional
Internal server error.

Payment Failed (S2S)

POST/v1/payments/failed

Called by the customer backend after receiving a PG payment-failure webhook. For funnel analysis, matching is performed identically and stored in the DB, but the response does not expose the attributed object (there is no point delivering click/install matching results to the client in a failure case).

Request Headers

Code copied to clipboard
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/json

Request Parameters

order_idstringRequired
The same order identifier as the initiated stage.
amountnumberRequired
The attempted payment amount. A number greater than 0.
currencystringRequired
ISO 4217 currency code (uppercase).
failure_reasonstringOptional
The failure reason received from the PG. Stored up to 500 chars.
transaction_idstringOptional
PG transaction number. Stored up to 200 chars.
payment_methodstringOptional
Payment method. Stored up to 50 chars.
metadataobjectOptional
Free-form additional data.

Request Example

Code copied to clipboard
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"
  }'

Response (200 OK)

Code copied to clipboard
{
  "success": true,
  "payment_event_id": "9876543230"
}

Response (200 OK, idempotent)

If failed is called again with the same order_id, it is handled idempotently (to handle PG webhook retries).

Code copied to clipboard
{
  "success": true,
  "payment_event_id": "9876543230",
  "idempotent": true
}

Response (409 Conflict)

Occurs when failed is called for an order that is already succeeded/refunded/canceled.

Code copied to clipboard
{
  "error": "payment already in terminal state",
  "existing_status": "succeeded"
}

Error Responses

400Bad RequestOptional
Invalid input format.
401UnauthorizedOptional
Missing X-Api-Key or X-Api-Secret header.
403ForbiddenOptional
Authentication failure. { "error": "인증에 실패했습니다." }
409ConflictOptional
Already exists with a different terminal status.
500Server ErrorOptional
Internal server error.

Record Partner Conversion — incl. non-payment (S2S)

POST/v1/conversions

Attributes conversions such as signups, leads, and purchases to promo partner performance (conversion rate / commission). Call server-to-server from your backend (never from the browser — secret exposure). Pass the ?vlref= value auto-appended to the destination URL as referral_key for explicit attribution; mobile apps are attributed automatically via device_id. The SDK's track() custom events feed the Events tab only and do not affect partner conversion rates.

Request Headers

Code copied to clipboard
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/json

Request Parameters

referral_keystringOptional
Partner referral key (≤32 chars). One of referral_key / device_id is required.
device_idstringOptional
Mobile SDK deviceId (≤100 chars) — auto attribution via install matching.
typestringRequired
signup | purchase | lead | custom.
order_idstringRequired
Idempotency key (1–100 chars, alphanumeric/-/_, unique per app). Re-calls return the existing conversion.
amountnumberOptional
Conversion amount. Defaults to 0 (0 allowed for signup/lead; negatives rejected).
currencystringRequired
Currency code — uppercase, required; 400 if missing (default allowlist: KRW·USD·JPY·EUR·GBP).
occurred_atstringOptional
Conversion timestamp (ISO 8601).
metadataobjectOptional
Free-form extra data.

Request Example

Code copied to clipboard
curl -X POST https://vialink.app/v1/conversions \
  -H "X-Api-Key: <your_api_key>" \
  -H "X-Api-Secret: <your_api_secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "referral_key": "aB3xK9",
    "type": "signup",
    "order_id": "SIGNUP-20260702-001",
    "amount": 0,
    "currency": "KRW"
  }'

Response (200 OK)

Code copied to clipboard
{
  "ok": true,
  "conversion_id": "412",
  "partner_id": "cmr2yofyb0003...",
  "attribution_method": "explicit_ref",
  "commission_amount": 0,
  "status": "confirmed"
}

attribution_method is explicit_ref (referral_key matched) | auto_device (install matched) | unmatched (no partner). Commission is snapshotted from partner/program settings at conversion time. For cancellations/refunds, call POST /v1/conversions/reverse with the order_id.

Error Responses

400Bad RequestOptional
Invalid input (type/order_id/amount, etc.).
401UnauthorizedOptional
Missing X-Api-Key or X-Api-Secret header.
404Not FoundOptional
Unknown referral_key.
409ConflictOptional
Concurrent duplicate insert (same order_id).
500Server ErrorOptional
Server error.

Reverse Partner Conversion (Refund) — S2S

POST/v1/conversions/reverse

Reverses (refunds) a conversion recorded via POST /v1/conversions. Call server-to-server from your backend. A reversed conversion changes to status: "reversed" and is excluded from partner performance aggregates (conversions / revenue / commission).

Request Headers

Code copied to clipboard
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/json

Request Parameters

order_idstringRequired
Idempotency key of the conversion to reverse (the value sent when recording it).

Request Example

Code copied to clipboard
curl -X POST https://vialink.app/v1/conversions/reverse \
  -H "X-Api-Key: <your_api_key>" \
  -H "X-Api-Secret: <your_api_secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ORDER-20260702-001"
  }'

Response (200 OK)

Code copied to clipboard
{
  "ok": true,
  "conversion_id": "412",
  "status": "reversed"
}

Idempotent response — already reversed

Re-calling for an already-reversed conversion returns the existing state as-is (idempotent: true).

Code copied to clipboard
{
  "ok": true,
  "conversion_id": "412",
  "status": "reversed",
  "idempotent": true
}

Error Responses

400Bad RequestOptional
Missing order_id.
401UnauthorizedOptional
Missing X-Api-Key or X-Api-Secret header.
403ForbiddenOptional
Authentication failed (invalid key/secret, inactive tenant).
404Not FoundOptional
No conversion found for the order_id.
500Server ErrorOptional
Server error.

Attribution Policy

The rules that determine which click/install a payment success/failure is attributed to. It follows a last-click (last_click) model, and matching is performed synchronously (eager) when /v1/payments/succeeded or /v1/payments/failed is called. The matching result is recorded immediately in the attributed_* columns of the payment_events table.

Window Settings

The attribution windows are stored in SystemConfig and can be adjusted from the admin system-config menu.

attribution.click_to_install_daysinteger (days)Optional
Click → install matching window. Only clicks within N days before the install time are attribution candidates. Default 7.
attribution.install_to_purchase_daysinteger (days)Optional
Install → payment matching window. Only installs within N days before the payment terminal time are attribution candidates. Default 30.
attribution.initiated_to_succeeded_hoursinteger (hours)Optional
initiated → succeeded/failed matching window. Only initiated rows within N hours before the terminal time are used to extract device_id. Default 24.
payments.allowed_currenciesstring[] (CSV)Optional
Allowed currency whitelist. ISO 4217 uppercase. Defaults to KRW, USD, EUR, JPY, etc. (see system defaults).

Matching Steps (pseudocode)

Code copied to clipboard
function attributePayment(tenant_id, order_id, terminal_at):
  cfg = loadAttributionConfig()  // read SystemConfig

  # 1) find the initiated row (most recent, within 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) find the install event (by device_id, within 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" (only install_event_id is filled)

  # 3) find the click event (by link_id, within click_to_install_days before the install)
  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"

Matching Failure Cases

no_initiated_or_no_deviceunmatchedOptional
There was no initiated call, or the SDK did not send device_id. Most common cause: SDK not initialized / exceeded the initiated_to_succeeded_hours window.
no_install_in_windowunmatchedOptional
No app.install event for the same device_id within install_to_purchase_days. Missing SDK install tracking or exceeded window.
organic_installunmatchedOptional
An install was captured but link_id is null (organic, e.g. direct store search).
last_click_install_onlymatchedOptional
The install is matched but there is no click event within click_to_install_days (e.g. pre-installed then activated by push).
errorunmatchedOptional
DB query failure. The response is 200 but attributed.method = error is shown and a Telegram alert is sent.
unmatchedunmatchedOptional
A fallback that is theoretically unreachable. Designed to branch into the cases above.
last_clickmatchedOptional
The only normal matching case. link_id, click_event_id, and install_event_id are all populated.

Android SDK

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

Installation

Add the dependency to Gradle.

Code copied to clipboard
// build.gradle.kts (app)
dependencies {
    implementation("com.vialink:sdk:1.0.0")
}

Initialization

Initialize the SDK in Application.onCreate().

Code copied to clipboard
// Application.kt
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

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

Event Tracking

Track custom events. The SDK flushes them in batches every 30 seconds.

Code copied to clipboard
// Purchase completed
ViaLinkSDK.track("purchase", mapOf(
    "product_id" to "12345",
    "revenue" to 29900,
    "currency" to "KRW"
))

// Sign up
ViaLinkSDK.track("signup")

// Add to cart
ViaLinkSDK.track("add_to_cart", mapOf("product_id" to "12345"))

Payment Tracking

Sends a payment attempt to the ViaLink server (/v1/payments/initiated). payment_method/metadata are optional, and device_info is collected automatically by the SDK. The paymentEventId in the response identifies the payment lifecycle for the same order_id. After receiving the PG callback, your backend must separately call /v1/payments/succeeded or /failed (S2S auth + bcrypt verification). Available from SDK 1.1.1+.

Code copied to clipboard
import com.vialink.sdk.ViaLinkSDK
import com.vialink.sdk.model.PaymentInitiatedArgs
import kotlinx.coroutines.launch

// Right before showing the payment screen (inside a 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) {
        // Input validation failed (orderId format, amount, currency)
    } catch (e: Exception) {
        // Network error
    }
}

iOS SDK

iOS 15.0+, Swift 5.9+ · Swift Package Manager

Installation

Add the Swift Package in Xcode.

Code copied to clipboard
Xcode > File > Add Package Dependencies
URL: https://github.com/aresjoydev/vialink-ios-sdk

Initialization

Initialize the SDK in AppDelegate or in the init of your SwiftUI App.

Code copied to clipboard
import ViaLinkCore

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

// Or 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)
                }
        }
    }
}

Event Tracking

Track custom events.

Code copied to clipboard
// Purchase completed
ViaLinkSDK.shared.track("purchase", data: [
    "product_id": "12345",
    "revenue": "29900",
    "currency": "KRW"
])

// Sign up
ViaLinkSDK.shared.track("signup")

Payment Tracking

Sends a payment attempt to the ViaLink server (/v1/payments/initiated). It is async/await based, and PaymentError lets you handle input-validation failures and network errors separately. After receiving the PG callback, your backend must separately call /v1/payments/succeeded or /failed (S2S auth + bcrypt verification). Available from SDK 1.1.1+.

Code copied to clipboard
import ViaLinkCore

// Right before showing the payment screen (inside an async context)
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 {
        // Other errors
    }
}

Web SDK

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

Installation

Install via npm or CDN.

Code copied to clipboard
npm install vialink-web-sdk

Initialization

Initialize the SDK. Events are still sent even when the user leaves the page.

Code copied to clipboard
import { ViaLinkWebSDK } from 'vialink-web-sdk';

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

Event Tracking

Track custom events including non-payment conversions (signup, lead, etc.). Sent in 30-second batches, plus via sendBeacon when the user leaves the page; aggregated in the dashboard Events tab. To attribute conversions to promo partner performance (conversion rate / commission), call POST /v1/conversions server-to-server instead of track — pass the ?vlref= value auto-appended to the destination URL as referral_key (see REST API docs).

Code copied to clipboard
// Purchase completed
sdk.track('purchase', {
  product_id: '12345',
  revenue: 29900,
  currency: 'KRW'
});

// Signup completed (non-payment conversion — aggregated in the Events tab)
sdk.track('signup_completed');

// Flush immediately
await sdk.flush();

Non-payment Conversion Tracking (partner attribution)

sdk.track() events feed the Events tab only and do not affect promo partner performance (conversion rate / commission). To attribute to partners: store the ?vlref= value auto-appended to the destination URL when users arrive via a partner link, then call POST /v1/conversions server-to-server once the conversion is confirmed (type: signup | purchase | lead | custom, idempotent order_id — see 'Record Partner Conversion' in the REST API section below).

Code copied to clipboard
// Recommended event names — for Events tab analytics
sdk.track('signup_completed');                     // Signup completed
sdk.track('lead_submitted', { form: 'consult' });  // Consultation/inquiry submitted
sdk.track('trial_started');                        // Free trial started

// For partner attribution — capture (store) vlref on landing, then
// call POST /v1/conversions from the backend once the conversion is confirmed (pass as referral_key)
const vlref = new URLSearchParams(location.search).get('vlref');
if (vlref) sessionStorage.setItem('vialink_ref', vlref);

Payment Tracking

Sends a payment attempt to the ViaLink server (/v1/payments/initiated). Available from SDK 1.1.0+. After receiving the PG callback, your backend must separately call /v1/payments/succeeded or /failed (S2S auth + bcrypt verification).

Code copied to clipboard
import { ViaLinkWebSDK } from 'vialink-web-sdk';

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

// Right before showing the payment screen:
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) {
  // Input validation failed or network error
  console.error(e);
}

Smart App Banner

Show a banner on mobile web that prompts users to install the app.

Code copied to clipboard
sdk.showBanner({
  title: 'View in App',
  description: 'Use the app for a faster experience',
  buttonText: 'Open',
  iosStoreUrl: 'https://apps.apple.com/app/id123456',
  androidStoreUrl: 'https://play.google.com/store/apps/...',
  position: 'bottom',
  theme: 'light'
});

// Hide the banner
sdk.hideBanner();

React Native SDK

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

Installation

Install via npm or yarn.

Code copied to clipboard
npm install vialink-react-native-sdk
# or
yarn add vialink-react-native-sdk

Initialization

Initialize the SDK at the top level of App.tsx.

Code copied to clipboard
import { ViaLinkSDK } from 'vialink-react-native-sdk';

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

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

  return <Navigation />;
}

Event Tracking

Track custom events.

Code copied to clipboard
// Purchase completed
ViaLinkSDK.shared.track('purchase', {
  product_id: '12345',
  revenue: 29900,
  currency: 'KRW'
});

// Sign up
ViaLinkSDK.shared.track('signup');

Payment Tracking

Sends a payment attempt to the ViaLink server (/v1/payments/initiated). It calls the Android/iOS native SDK's payment.initiated through the native bridge (SDK 2.1.0+). After receiving the PG callback, your backend must separately call /v1/payments/succeeded or /failed.

Code copied to clipboard
import { ViaLinkSDK } from 'vialink-react-native-sdk';

// Right before showing the payment screen:
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) {
  // Input validation failed or network error
}

Platform Setup

Deep link configuration is required separately on iOS and Android.

Code copied to clipboard
// 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)

Installation

Add the package via UPM.

Code copied to clipboard
Window > Package Manager > + > Add package from git URL:
https://github.com/aresjoydev/vialink-unity-sdk.git

Initialization

Place the ViaLinkSDK prefab in your first scene and initialize it.

Code copied to clipboard
using ViaLink;

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

Event Tracking

Track custom events.

Code copied to clipboard
// In-app purchase
ViaLinkSDK.Instance.TrackEvent("purchase",
    new Dictionary<string, object>
    {
        { "product_id", "gem_pack_100" },
        { "revenue", 4900 },
        { "currency", "KRW" }
    });

// Level cleared
ViaLinkSDK.Instance.TrackEvent("level_clear",
    new Dictionary<string, object>
    {
        { "level", 10 }
    });

Payment Tracking

Sends a payment attempt to the ViaLink server (/v1/payments/initiated). The Unity SDK uses a callback pattern (onSuccess/onError) (SDK 1.1.0+). After receiving the PG callback, your backend must separately call /v1/payments/succeeded or /failed.

Code copied to clipboard
using ViaLink.SDK;

// Right before showing the payment screen (inside a 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($"Payment attempt failed: {error}");
    }
);

Flutter SDK

Flutter 3.19+, Dart 3.11+ · pub.dev

Installation

Add the dependency to pubspec.yaml.

Code copied to clipboard
flutter pub add vialink_flutter_plugin

Initialization

Initialize the SDK when the app starts.

Code copied to clipboard
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());
}

Event Tracking

Track custom events.

Code copied to clipboard
// Purchase completed
ViaLinkSDK.instance.track('purchase', data: {
  'product_id': '12345',
  'revenue': 29900,
  'currency': 'KRW',
});

// Sign up
ViaLinkSDK.instance.track('signup');

Payment Tracking

Sends a payment attempt to the ViaLink server (/v1/payments/initiated). The Dart facade calls the native SDK's payment.initiated through the native plugin's (Android/iOS) MethodChannel (SDK 2.1.0+). After receiving the PG callback, your backend must separately call /v1/payments/succeeded or /failed.

Code copied to clipboard
import 'package:vialink_flutter_plugin/vialink_flutter_plugin.dart';

// Right before showing the payment screen:
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) {
  // Input validation failed
  print(e);
} catch (e) {
  // PlatformException or other errors
}

Platform Setup

Deep link configuration is required separately on iOS and Android.

Code copied to clipboard
# iOS: ios/Runner/Runner.entitlements
# Add 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>