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/succeededPOST /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.
Links / Domains
Can I specify the Short Code myself?
You can specify it with the customCode parameter when creating a link. If not specified, a 6-character base36 code is generated automatically.
You can check for duplicates in advance with POST /api/links/check-short-code.
Can I edit a link after it's been created?
Yes, via PUT /api/links/:id or from the dashboard. The editable fields are as follows.
name,description,tagsdeeplinkPath,deeplinkDataiosUrl,androidUrl,webUrlogTitle,ogDescription,ogImageUrlcampaign,channel,featureexpiresAt
However, linkType itself cannot be changed.
How are link expiration, deactivation, and deletion handled?
- Expiration — Once the
expiresAttime passes, redirects are blocked - Deactivation — You can suspend a link temporarily via
suspendedAt - Deletion — Calling
DELETE /api/links/:idperforms a soft delete that recordsdeletedAt. If recovery is needed, it can be handled from the admin panel
Can I connect a custom domain (e.g., go.mycompany.com)?
Register it under App → Domains in the dashboard. Once you set the CNAME target to edge.vialink.app, DNS verification and SSL issuance proceed automatically.
Domain status is shown through the following stages.
pending_dns— Waiting for DNS verificationdns_verified— DNS verification completessl_provisioning— SSL certificate being issuedactive— Ready to use
Custom domains are available on plans that include the customDomain feature.
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.
- Android —
GET /.well-known/assetlinks.json. Generated dynamically from the registered package name (Play Store · ONE Store) and SHA-256 fingerprint - iOS —
GET /.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 eventPOST /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/succeededor/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_eventstable'sattributed_*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 missingno_install_in_window— No app.install event from the same device within the windoworganic_install— An install was captured, but it's an organic install with no link_idlast_click_install_only— The install was matched, but there is no click eventerror— 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.
Deep Links
I've only entered app info (Bundle ID, etc.) and haven't installed the SDK yet — I'm testing, but why doesn't the link reach the app on certain platforms?
Simply "opening the app" is up to the OS, but the logic that checks whether "this app is actually ready to receive this link" differs by platform.
- Symptom — Even if the app opens through the link, an app without the SDK installed can't interpret the parameters the user clicked through (e.g., a referral code, a specific product number), so it stalls on the main screen or throws an error.
- Platform differences — This is especially true when using deferred deep linking (where a user who didn't have the app installed lands on the intended screen after installing): without the SDK inside the app, the store's install information can't be matched with the link-click information, breaking the flow.
- Solution — App settings alone only get you halfway there. The deep link SDK must be embedded inside the app to act as the "antenna" that receives the system's calls — only then can you reliably reach the intended in-app page across every platform.
I've finished setting up Universal Links, but the app still won't launch from Instagram, Facebook, or Threads. What could be wrong?
Is your Custom URL Scheme missing? (The key to escaping social apps)
Closed social platforms like Instagram, Facebook, and Threads don't accept Universal Links as-is — they keep the user trapped inside their own in-app browser.
- Symptom — If you've only set up Universal Links and skipped the Custom URL Scheme setting, tapping "Open in app" inside the in-app browser does nothing, because there's no "address" for it to call the app with.
- Solution — Your app's own unique URL Scheme must be registered in the app settings (
Info.plist), and that same Scheme value must also be entered correctly in the deep link SDK configuration. Only then can you break through the closed platform's walls and open the app.
I updated my Universal Links settings — why isn't the change applied right away?
This is very likely due to caching rather than a configuration error. Universal Links rely on a structure where Apple acts as an intermediary "guarantor" for security and performance, and this information takes real time to update.
1. Why doesn't it apply right away? (Three-stage cache structure)
Information passes through three stages before Universal Links work. Each stage retains the information for a different length of time.
- Stage 1 — Apple's CDN servers (up to 48 hours) — Apple doesn't constantly check every app's configuration worldwide. It stores the information in its own CDN (data store) and updates it on a roughly 24–48 hour cycle.
- Stage 2 — The user's iOS device (up to 1 week) — Instead of checking with the server every time, iOS stores the information locally on the device. This is typically refreshed about once a week.
- Stage 3 — Platform-specific in-app browser cache — Apps like Naver or Facebook keep their own browser cache, and may still remember a previously failed connection.
2. If you want to test immediately (for development/QA)
If you can't wait for the production cache to clear, try the methods below.
- Method 1 — Use Developer Mode (most recommended) — Enable [Settings > Developer > Associated Domains Development] on your iPhone. Appending the
?mode=developerparameter to the link fetches the latest information directly from the server, bypassing the Apple CDN. - Method 2 — Reinstall the app — Deleting and reinstalling the app resets some of the cached information on the device, letting it pick up the new settings.
- Method 3 — Add a random parameter to the link — Instead of
vialink.app/abcd, visiting an address you've never used before, such asvialink.app/abcd?refresh=1, can sometimes bypass the cache.
Authentication
All API calls are authenticated with the X-API-Key header. Registering an app in the dashboard issues an API Key.
Request Headers
X-API-Key: <your_api_key>Base URL
The base URL for all API requests.
https://vialink.appCreate Link
Creates a new deep link.
Request Parameters
linkType mode comparison
- ·
static(default) — A fixed single entry point. If a token is appended to the issued URL, it is treated as 404 (blocking search engine indexing). - ·
dynamic— Appending/yourValueor?key=valueto the issued URL automatically passes that value to the click history and SDK response.
Request Example
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": "Summer Sale Special",
"ogDescription": "Check it out now!",
"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 Spring IG ad",
"description": "Instagram story ad only. Expires end of June."
}Response (201 Created)
{
"id": 1,
"shortCode": "summer-sale-2026",
"shortUrl": "https://vialink.app/{your-slug}/summer-sale-2026",
"deeplinkPath": "/product/12345",
"linkType": "static",
"name": "2026 Spring IG ad",
"description": "Instagram story ad only. Expires end of June.",
"createdAt": "2026-04-06T12:00:00Z"
}Error (400 Bad Request)
When a value other than dynamic/static is explicitly sent for linkType (omitting the field automatically stores it as static):
{
"error": "linkType must be 'dynamic' or 'static'."
}Error (409 Conflict)
When customCode is already in use within the same app (checked case-insensitively):
{
"error": "This short code is already in use."
}Get Link
Retrieves the full information for a specific link.
Path Parameters
Response (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": "Summer Sale Special",
"ogDescription": "Check it out now!",
"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 Spring IG ad",
"description": "Instagram story ad only. Expires end of June.",
"createdAt": "2026-04-06T12:00:00Z",
"updatedAt": "2026-04-06T12:00:00Z"
}Update Link
Updates an existing link. Send only the fields you want to change.
Request Parameters
linkType cannot be changed
If the request body of this endpoint includes a linkType key, it is rejected with 400. To change the mode, create a new link.
Partner-assigned links cannot change destination
Partner-assigned tracking links (partnerId set) cannot change destination fields (deeplinkPath, deeplinkData, iosUrl, androidUrl, webUrl); attempting to do so returns 400. Metadata fields such as campaign/channel and partner reassignment (partnerId) can be edited.
Request Example
PUT /api/links/1
Content-Type: application/json
X-API-Key: <your_api_key>
{
"ogTitle": "Updated title",
"webUrl": "https://www.example.com/new-page",
"name": "2026 Summer ad",
"description": "Updated for the summer campaign."
}Response (200 OK)
The full updated link object is returned (including name and description).
Error (400 Bad Request)
When the request body includes a linkType key:
{
"error": "Mode cannot be changed. Please create a new link."
}Delete Link
Deletes a link (soft delete). The deleted link's URL no longer works.
Path Parameters
Partner-assigned links cannot be deleted
Partner-assigned tracking links (partnerId set) cannot be deleted via this endpoint and return 403 (to preserve attribution and past clicks/conversions). To end the relationship, use the partner status (pause/remove).
Response (200 OK)
{
"success": true
}List Links
Retrieves the list of created links.
Returns the list of links for the app authenticated by the API Key.
Response (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 Spring IG ad",
"description": "Instagram story ad only. Expires end of June.",
"createdAt": "2026-04-06T12:00:00Z"
}
],
"total": 50
}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
Request Example
GET /api/links/check-short-code?tenantId=tenant_abc&code=summer-sale-2026Response (200 OK — available)
{
"available": true
}Response (200 OK — already in use)
{
"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\}/PTMLWTandvialink.app/\{slug\}/ptmlwtmatch the same link.
Payment Initiated (SDK)
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
X-Api-Key: <your_api_key>
Content-Type: application/jsonRequest Parameters
Request Example
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)
{
"success": true,
"payment_event_id": "9876543210"
}Error Responses
Payment Succeeded (S2S)
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
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/jsonapiSecret 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
Request Example
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)
{
"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)
{
"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).
{
"error": "payment already in terminal state",
"existing_status": "failed"
}attribution_method values
The 7 possible values of attributed.method.
Error Responses
Payment Failed (S2S)
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
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/jsonRequest Parameters
Request Example
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)
{
"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).
{
"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.
{
"error": "payment already in terminal state",
"existing_status": "succeeded"
}Error Responses
Record Partner Conversion — incl. non-payment (S2S)
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
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/jsonRequest Parameters
Request Example
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)
{
"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
Reverse Partner Conversion (Refund) — S2S
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
X-Api-Key: <your_api_key>
X-Api-Secret: <your_api_secret>
Content-Type: application/jsonRequest Parameters
Request Example
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)
{
"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).
{
"ok": true,
"conversion_id": "412",
"status": "reversed",
"idempotent": true
}Error Responses
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.
Matching Steps (pseudocode)
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
Installation
Add the dependency to Gradle.
// build.gradle.kts (app)
dependencies {
implementation("com.vialink:sdk:1.0.0")
}Initialization
Initialize the SDK in Application.onCreate().
// Application.kt
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
ViaLinkSDK.init(this, "YOUR_API_KEY")
}
}Deep Link Handling
Register a deep link callback and handle the launch Intent in your Activity. SDK 3.1+: the callback also fires on normal launches (e.g. tapping the app icon) with data == null.
// Register a deep link callback (SDK 3.1+: (DeepLinkData?) -> Unit)
// Always called once on handleIntent:
// - data != null : App Link entry → route using data.path
// - data == null : normal launch (icon tap etc.) → keep the default screen
ViaLinkSDK.onDeepLink { data ->
if (data == null) {
Log.d("ViaLink", "Normal launch — keep the default screen")
return@onDeepLink
}
Log.d("ViaLink", "path: ${data.path}")
Log.d("ViaLink", "params: ${data.params}")
}
// Deferred deep link callback (SDK 3.2+: called once on every app launch)
// - First launch: called as soon as the match result is decided, within 5s (error on timeout/failure)
// - Non-first launch: called immediately with (null, null) — no /v1/open call (same meaning as organic)
ViaLinkSDK.onDeferredDeepLink { data, error ->
when {
error != null -> Log.w("ViaLink", "Deferred match failed: ${error.code}")
data == null -> Log.d("ViaLink", "Organic or non-first launch — normal entry")
else -> Log.d("ViaLink", "Deferred: ${data.path}")
}
}
// Handle the launch Intent in the Activity (SDK 3.1+: callback also fires on normal launches)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ViaLinkSDK.handleIntent(intent) // Always called
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
ViaLinkSDK.handleIntent(intent)
}
}Event Tracking
Track custom events. The SDK flushes them in batches every 30 seconds.
// 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+.
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
}
}Create Link
Create a deep link inside the app and share it. You can pass every option the server accepts — fallback URLs, OG meta tags, channel/tags/expiry, and more — as named arguments. linkType defaults to "static" (a single fixed entry point, no tracking); specify "dynamic" when you need click tracking.
lifecycleScope.launch {
val result = ViaLinkSDK.createLink(
path = "/product/12345",
data = mapOf("promo_code" to "FRIEND_SHARE"),
campaign = "referral",
linkType = "dynamic", // "dynamic" when you need click tracking (omit for "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 = "Summer Sale",
ogDescription = "Check it out now!",
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, "Share"))
}
}Create Link (calling from Java)
If your app source is Java you cannot call the suspend fun directly. Use the Callback/CompletableFuture wrappers from SDK 1.4.0+, and pass full options via the CreateLinkOptions Builder from 1.5.0+.
import com.vialink.sdk.ViaLinkSDK;
import com.vialink.sdk.model.CreateLinkOptions;
import java.util.Map;
import java.util.List;
// Full options — using the 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("Summer Sale")
.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 -> { /* success (main thread) */ },
err -> Log.e("ViaLink", "Failed to create link", err)
);
// CompletableFuture style (minSdk 24+)
ViaLinkSDK.createLinkFuture("/product/12345", opts)
.thenAccept(shortUrl -> { /* success */ })
.exceptionally(err -> { /* failure */ return null; });
// Simple call — when you don't need fallback/OG options
ViaLinkSDK.createLinkAsync(
"/product/12345",
Map.of("promo_code", "FRIEND_SHARE"),
"referral",
"dynamic",
shortUrl -> {},
err -> {}
);App Links Setup
Add an App Links intent filter to AndroidManifest.xml.
<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>Installation
Add the Swift Package in Xcode.
Xcode > File > Add Package Dependencies
URL: https://github.com/aresjoydev/vialink-ios-sdkInitialization
Initialize the SDK in AppDelegate or in the init of your SwiftUI App.
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)
}
}
}
}Deep Link Handling
Register a deep link callback and handle Universal Links in the SceneDelegate. SDK 3.1+: the callback also fires on normal launches (e.g. tapping the app icon) with data == nil.
// Register a deep link callback (SDK 3.1+: (DeepLinkData?) -> Void)
// Always called once when notified of app entry:
// - data != nil : Universal Link entry → route using data.path
// - data == nil : normal launch (icon tap etc.) → keep the default screen
ViaLinkSDK.shared.onDeepLink { data in
guard let data = data else {
print("Normal launch — keep the default screen")
return
}
print("path: \(data.path)")
print("params: \(data.params)")
}
// Deferred deep link callback (SDK 3.2+: called once on every app launch)
// - First launch: called as soon as the match result is decided, within 5s (error on timeout/failure)
// - Non-first launch: called immediately with (nil, nil) — no /v1/open call (same meaning as organic)
ViaLinkSDK.shared.onDeferredDeepLink { data, error in
if let error = error {
print("Deferred match failed: \(error.code.rawValue)")
} else if let data = data {
print("Deferred: \(data.path)")
} else {
print("Organic or non-first launch — normal entry")
}
}
// Handle Universal Links + normal-launch notification in SceneDelegate (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() // Normal launch
}
}
func scene(_ scene: UIScene,
continue userActivity: NSUserActivity) {
ViaLinkSDK.shared.handleUniversalLink(userActivity)
}Event Tracking
Track custom events.
// 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+.
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
}
}Create Link
Create a deep link inside the app. You can pass every option the server accepts — fallback URLs, OG meta tags, channel/tags/expiry, and more — as named arguments.
let shortUrl = try await ViaLinkSDK.shared.createLink(
path: "/product/12345",
data: ["promo_code": "FRIEND_SHARE"],
campaign: "referral",
linkType: "dynamic", // "dynamic" when you need click tracking (omit for "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: "Summer Sale",
ogDescription: "Check it out now!",
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 Setup
Configure Associated Domains in Xcode.
1. Xcode > Target > Signing & Capabilities
2. + Capability > Associated Domains
3. Add domain: applinks:vialink.appInstallation
Install via npm or CDN.
npm install vialink-web-sdkInitialization
Initialize the SDK. Events are still sent even when the user leaves the page.
import { ViaLinkWebSDK } from 'vialink-web-sdk';
const sdk = ViaLinkWebSDK.init({
apiKey: 'YOUR_API_KEY'
});Deep Link Handling
Register a deep link callback or extract it directly from the current URL. SDK 3.1+: onDeepLink fires the callback once immediately on registration (page load = entry).
// Callback pattern (SDK 3.1+: parses the current URL once immediately on registration)
// - data != null : ViaLink deep link format entry → route using data.path
// - data == null : normal entry (landing page etc.) → keep the default route
sdk.onDeepLink((data) => {
if (!data) {
console.log('Normal entry — keep the default route');
return;
}
console.log('path:', data.path);
console.log('params:', data.params);
// window.location.href = data.path or router navigate
});
// Synchronous extraction pattern (immediately at page load time)
const data = sdk.getDeepLinkData();
if (data) {
console.log('shortCode:', data.shortCode);
}
// Parse an arbitrary URL using the static method
const parsed = ViaLinkWebSDK.parseDeepLinkFromURL(
'https://vialink.app/{your-slug}/xYz12?ref=share'
);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).
// 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).
// 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).
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);
}Create Link
Create a deep link on the web. Pass additional options — fallback URLs, OG meta tags, channel/tags/expiry, and more — as the 5th argument (an object).
const shortUrl = await sdk.createLink(
'/product/12345',
{ promo_code: 'FRIEND_SHARE' },
'referral',
'dynamic', // "dynamic" when you need click tracking (omit for '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: 'Summer Sale',
ogDescription: 'Check it out now!',
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"Installation
Install via npm or yarn.
npm install vialink-react-native-sdk
# or
yarn add vialink-react-native-sdkInitialization
Initialize the SDK at the top level of App.tsx.
import { ViaLinkSDK } from 'vialink-react-native-sdk';
// App.tsx
function App() {
useEffect(() => {
ViaLinkSDK.shared.configure('YOUR_API_KEY');
return () => {
ViaLinkSDK.shared.destroy();
};
}, []);
return <Navigation />;
}Deep Link Handling
Register a deep link callback. SDK 3.1+: the callback also fires on normal launches (e.g. tapping the app icon) with data === null.
// Register a deep link callback (SDK 3.1+: (data: DeepLinkData | null) => void)
// Always called once when notified of native app entry:
// - data != null : App Links / Universal Links entry → route using data.path
// - data == null : normal entry (icon tap etc.) → keep the default route
ViaLinkSDK.shared.onDeepLink((data) => {
if (!data) {
console.log('Normal entry — keep the default route');
return;
}
console.log('path:', data.path);
console.log('params:', data.params);
navigation.navigate(data.path, data.params);
});
// Deferred deep link callback (SDK 3.2+: called once on every app launch)
// - First launch: /v1/open match result delivered within 5s (error on timeout/failure)
// - Non-first launch: called immediately with (null, null) — no network call (same meaning as organic)
ViaLinkSDK.shared.onDeferredDeepLink((data, error) => {
if (error) {
console.warn('Deferred match failed:', error.code);
return;
}
if (!data) {
console.log('Organic or non-first launch — normal entry');
return;
}
navigation.navigate(data.path, data.params);
});Event Tracking
Track custom events.
// 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.
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
}Create Link
Create a deep link inside the app and share it. Pass additional options — fallback URLs, OG, channel, and more — as the 5th argument (an object).
const shortUrl = await ViaLinkSDK.shared.createLink(
'/product/12345',
{ promo_code: 'FRIEND_SHARE' },
'referral',
'dynamic', // "dynamic" when you need click tracking (omit for '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: 'Summer Sale',
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 });Platform Setup
Deep link configuration is required separately on iOS and 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"]
}]
}
}
}Installation
Add the package via UPM.
Window > Package Manager > + > Add package from git URL:
https://github.com/aresjoydev/vialink-unity-sdk.gitInitialization
Place the ViaLinkSDK prefab in your first scene and initialize it.
using ViaLink;
public class GameManager : MonoBehaviour
{
void Start()
{
ViaLinkSDK.Instance.Initialize("YOUR_API_KEY");
}
}Deep Link Handling
Subscribe to the event to handle deep links. SDK 3.1+: the event also fires on normal launches (e.g. tapping the app icon) with data == null.
// Deep link callback (SDK 3.1+: also fires on normal launches — data == null)
ViaLinkSDK.Instance.OnDeepLink += (data) =>
{
if (data == null)
{
Debug.Log("Normal launch — keep the default screen");
return;
}
Debug.Log($"path: {data.Path}");
Debug.Log($"params: {data.Params}");
SceneManager.LoadScene(data.Path);
};
// Deferred deep link callback (SDK 3.2+: fires once on every app launch)
// - First launch: /v1/open match result delivered within 5s (error on timeout/failure)
// - Non-first launch: called immediately with (null, null) — no network call (same meaning as organic)
ViaLinkSDK.Instance.OnDeferredDeepLink += (data, error) =>
{
if (error != null)
{
Debug.LogWarning($"Deferred match failed: {error.Code}");
return;
}
if (data == null)
{
Debug.Log("Organic or non-first launch — normal entry");
return;
}
Debug.Log($"Deferred deep link: {data.Path}");
SceneManager.LoadScene(data.Path);
};Event Tracking
Track custom events.
// 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.
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}");
}
);Create Link
Create an invite link inside the game. You can pass every option the server accepts — fallback URLs, OG meta tags, channel/tags/expiry, and more — as named arguments.
ViaLinkSDK.Instance.CreateLink(
path: "/invite",
data: new Dictionary<string, object>
{
{ "inviter_id", PlayerId },
{ "reward", "gem_50" }
},
campaign: "friend_invite",
linkType: "dynamic", // "dynamic" when you need click tracking (omit for "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: "Invite a Friend — Get a Reward",
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("Invite link copied!");
},
onError: (error) =>
{
Debug.LogError($"Failed to create link: {error}");
}
);Installation
Add the dependency to pubspec.yaml.
flutter pub add vialink_flutter_pluginInitialization
Initialize the SDK when the app starts.
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());
}Deep Link Handling
The SDK receives deep links automatically — you only need to register a callback. SDK 3.1+: the callback also fires on normal launches (e.g. tapping the app icon) with data == null.
// Deep link callback (SDK 3.1+: (DeepLinkData?) -> void)
// Always called once when notified of app entry:
// - data != null : App Links / Universal Links entry → route using data.path
// - data == null : normal entry (icon tap etc.) → keep the default route
ViaLinkSDK.instance.onDeepLink((data) {
if (data == null) {
print('Normal entry — keep the default route');
return;
}
print('path: ${data.path}');
print('params: ${data.params}');
Navigator.pushNamed(context, data.path,
arguments: data.params);
});
// Deferred deep link callback (SDK 3.2+: called once on every app launch)
// - First launch: /v1/open match result delivered within 5s (error on timeout/failure)
// - Non-first launch: called immediately with (null, null) — no network call (same meaning as organic)
ViaLinkSDK.instance.onDeferredDeepLink((data, error) {
if (error != null) {
debugPrint('Deferred match failed: ${error.code}');
return;
}
if (data == null) {
debugPrint('Organic or non-first launch — normal entry');
return;
}
Navigator.pushNamed(context, data.path, arguments: data.params);
});Event Tracking
Track custom events.
// 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.
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
}Create Link
Create a deep link inside the app. You can pass every option the server accepts — fallback URLs, OG meta tags, channel/tags/expiry, and more — as named arguments.
final shortUrl = await ViaLinkSDK.instance.createLink(
path: '/product/12345',
data: {'promo_code': 'FRIEND_SHARE'},
campaign: 'referral',
linkType: 'dynamic', // "dynamic" when you need click tracking (omit for '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: 'Summer Sale',
ogDescription: 'Check it out now!',
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);Platform Setup
Deep link configuration is required separately on iOS and Android.
# 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>