Home Developer Setup

Developer Setup

Platform-specific setup guides for integrating LinkMe into apps and services
By Tomas Radvansky
7 articles

Developer Setup

Setup Guides Choose your platform to get started with LinkMe. Each guide walks you through installing the SDK, configuring your app or project, and making your first request so you can resolve smart links and universal links from your application. Platform quick links - iOS — Xcode project setup, Associated Domains, and Swift or Objective-C integration - Android — Gradle dependency, intent filters, and Kotlin or Java usage - React Native — Linking and native module setup for iOS and Android - Flutter — Dart package installation and platform-specific configuration - Node.js — NPM package, API client, and server-side link resolution - Web — Browser SDK for redirects and optional deferred deep linking After you complete your platform guide, see the developer overview for API behavior, auth for API keys, and OpenAPI for the full REST specification. For universal links and app links hosting, ensure your domain serves the correct AASA and assetlinks files; the Universal Links Validator can check your configuration. What each setup guide covers Every platform guide in this hub follows a similar structure: install the SDK or client, configure your project (e.g. Associated Domains on iOS, intent filters on Android), and resolve a link from your app or server. We include code snippets, required permissions, and links to the REST API and OpenAPI spec where relevant. If you use React Native or Flutter, the guides cover both iOS and Android from a single codebase. Next steps after setup Once your app or service can resolve links, explore developer overview for redirect behavior, UTM handling, and fallbacks. Use auth to create and manage API keys for server-side or headless use. For universal links and app links, ensure your domain hosts valid AASA and assetlinks files; our Universal Links Validator checks format, content type, and size so you can fix issues before going live.

Last updated on Apr 04, 2026

Android Setup Guide

Android setup tutorial Recreate the Kotlin example app that ships with the SDK, then tailor it to your product. Before you start - LinkMe workspace + app - Android package name and SHA-256 fingerprint (debug + release if possible) - Access to update Gradle files and the manifest 1. Configure your LinkMe app 1. Portal → Apps → Create app (or open an existing entry). 2. Enter the Android package ID and paste the SHA-256 certificate fingerprint. 3. Generate an API key in App Settings → API Keys and copy both appId and appKey. 4. Confirm Install Referrer stay enabled—Android uses it for deferred attribution automatically. 2. Install the SDK and dependencies // settings.gradle dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://jitpack.io") } } } // app/build.gradle dependencies { implementation("com.github.r-dev-limited:li-nk.me-android-sdk:0.2.8") } JitPack publishes every tag automatically—check jitpack.io/#r-dev-limited/li-nk.me-android-sdk if you need build details. linkmekit already bundles the Install Referrer and Ad ID libraries, so no extra dependencies are needed unless you want to collect Ad ID (requires user consent + com.google.android.gms.permission.AD_ID). 3. Declare hosts in the manifest <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="links.yourco.com" /> </intent-filter> <!-- Optional custom scheme --> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="yourapp" /> </intent-filter> </activity> LinkMe hosts assetlinks.json automatically for the domains configured in App Settings. Manual deep-link config mapping Use these values for your Android deep-link setup: { "hosts": ["links.yourco.com"], "associatedDomains": ["links.yourco.com"], "schemes": ["yourapp"] } What each field does and why it must be set: - hosts: your HTTPS deep-link domain(s). Map each host to an App Links intent filter (android:autoVerify="true"). - associatedDomains: keep this aligned with hosts for cross-platform consistency; on Android this is the same host list you verify via Digital Asset Links. - schemes: fallback custom URL scheme(s) for explicit scheme opens. Why this is required: - Without hosts, Android has no verified App Links target and HTTPS links will open in browser/disambiguation flows. - Without schemes, yourapp://... links cannot route into your app. - If host/scheme values do not match your manifest, deep-link routing will be unreliable. Apply these values in the manifest: - hosts / associatedDomains → HTTPS App Links intent filter: <data android:scheme="https" android:host="links.yourco.com" /> - schemes → custom scheme intent filter: <data android:scheme="yourapp" /> 4. Initialize and handle intents class App : Application() { override fun onCreate() { super.onCreate() LinkMe.shared.configure( context = this, config = LinkMe.Config( baseUrl = "https://links.yourco.com", appId = BuildConfig.LINKME_APP_ID, appKey = BuildConfig.LINKME_APP_KEY, sendDeviceInfo = true, includeAdvertisingId = false, debug = BuildConfig.DEBUG, ), ) LinkMe.shared.getInitialLink { payload -> payload?.let(::routeUser) } LinkMe.shared.claimDeferredIfAvailable(this) { deferred -> deferred?.let(::routeUser) } } } class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) LinkMe.shared.handleIntent(intent) LinkMe.shared.addListener { payload -> routeUser(payload) } } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) LinkMe.shared.onNewIntent(intent) } } This mirrors the code path inside sdks/android/example-app and ensures direct, background, and deferred flows all resolve to the same routeUser helper. 5. Test with the example app 1. Copy sdks/android/example-app outside the monorepo or open it directly in Android Studio. 2. Set LINKME_APP_ID/LINKME_APP_KEY in local.properties (or Gradle properties). 3. Run the app and click a LinkMe slug to confirm direct opens. For deferred tests, install from the Play Store simulator by opening the link, uninstalling, then reinstalling via the Play Store intent. Troubleshooting - adb shell pm get-app-links --user cur com.example.app shows whether Android trusts your domain. - Run adb shell pm verify-app-links --re-verify com.example.app after changing fingerprints or domains. - If links still fall back to the browser, allow them manually: adb shell pm set-app-links --package com.example.app --allow all (helpful on test devices). Next, move to the Android SDK reference for detailed API and payload documentation. Common Issues Links open Play Store: - Verify intent-filter includes android:autoVerify="true" - Ensure app was rebuilt and reinstalled after manifest changes - Check domain verification status - Verify assetlinks.json is accessible and correctly formatted Verification timing: - Automatic verification occurs on install (within a few seconds) - If verification fails, status is cached until manually re-verified or app reinstalled - Existing users who installed before verification may need to enable manually

Last updated on Apr 04, 2026

Web Setup Guide

Web setup tutorial Turn any SPA or SSR site into a LinkMe-aware experience. The example below focuses on Next.js (App Router) because it matches our marketing site, but the same SDK works everywhere. Before you start - LinkMe app with an API key (read-only scope is enough for the browser) - Custom domain or default li-nk.me domain for links - Modern build tooling (Next.js 13+, Vite, etc.) 1. Install the SDK npm install @li-nk.me/web-sdk Package: @li-nk.me/web-sdk The package is zero-dependency TypeScript that works in any framework—even vanilla JS. 2. Configure environment variables Expose your app ID (and optional read-only key) to the client: # .env.local NEXT_PUBLIC_LINKME_APP_ID=app_123 NEXT_PUBLIC_LINKME_APP_READ_KEY=app_live_read_... Use public prefixes as required by your framework (e.g., VITE_ for Vite, NEXT_PUBLIC_ for Next.js). Tip: pass debug: process.env.NODE_ENV !== 'production' to configure() while developing to see URL parsing and deferred-claim requests logged in the browser console. 3. Bootstrap in Next.js (App Router example) 'use client'; import { ReactNode, useEffect } from 'react'; import { configure, resolveFromUrl, onLink, claimDeferredIfAvailable } from '@li-nk.me/web-sdk'; import { useRouter } from 'next/navigation'; export function LinkMeProvider({ children }: { children: ReactNode }) { const router = useRouter(); useEffect(() => { let mounted = true; let unsubscribe: { remove: () => void } | null = null; (async () => { await configure({ baseUrl: 'https://links.yourco.com', appId: process.env.NEXT_PUBLIC_LINKME_APP_ID!, appKey: process.env.NEXT_PUBLIC_LINKME_APP_READ_KEY, }); const initial = await resolveFromUrl(); if (mounted && initial?.path) { router.replace(initial.path.startsWith('/') ? initial.path : `/${initial.path}`); } else { const deferred = await claimDeferredIfAvailable(); if (mounted && deferred?.path) { router.replace(deferred.path.startsWith('/') ? deferred.path : `/${deferred.path}`); } } unsubscribe = onLink((payload) => { if (!payload?.path) return; router.replace(payload.path.startsWith('/') ? payload.path : `/${payload.path}`); }); })(); return () => { mounted = false; unsubscribe?.remove(); }; }, [router]); return <>{children}</>; } Wrap your app layout: // app/layout.tsx import { LinkMeProvider } from './LinkMeProvider'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <body> <LinkMeProvider>{children}</LinkMeProvider> </body> </html> ); } 4. SPA frameworks (React/Vue/Svelte) Call the same bootstrap logic from a custom hook or effect: import { useEffect } from 'react'; import { configure, resolveFromUrl, onLink } from '@li-nk.me/web-sdk'; import { useNavigate } from 'react-router-dom'; export function useLinkMe() { const navigate = useNavigate(); useEffect(() => { let cleanup: (() => void) | null = null; (async () => { await configure({ appId: import.meta.env.VITE_LINKME_APP_ID }); const initial = await resolveFromUrl(); if (initial?.path) navigate(initial.path); const sub = onLink((payload) => payload?.path && navigate(payload.path)); cleanup = () => sub.remove(); })(); return () => cleanup?.(); }, [navigate]); } 5. Test locally - Run npm run dev, click a LinkMe slug that points to your domain, and confirm the page swaps without a full reload. - Open the same slug in an incognito window to validate deferred flow (the SDK will call /api/deferred/claim). - Use your browser's network tab to confirm cid claims return payload JSON. Next steps - Review the Web SDK reference for every export (configure, onLink, resolveFromUrl, etc.). - Pair this with your marketing site or portal by hosting a minimal provider component like the one above. Svelte import { onMount } from 'svelte'; import { goto } from '$app/navigation'; import { configure, resolveFromUrl, onLink } from '@li-nk.me/web-sdk'; onMount(() => { let active = true; (async () => { await configure({ appId: import.meta.env.VITE_LINKME_APP_ID, appKey: import.meta.env.VITE_LINKME_APP_READ_KEY, }); const initial = await resolveFromUrl(); if (active && initial?.path) goto(initial.path, { replaceState: true }); })(); const { remove } = onLink((payload) => { if (payload?.path) goto(payload.path); }); return () => { active = false; remove(); }; }); Angular import { Injectable, OnDestroy } from '@angular/core'; import { Router } from '@angular/router'; import { configure, onLink, resolveFromUrl } from '@li-nk.me/web-sdk'; @Injectable({ providedIn: 'root' }) export class LinkMeService implements OnDestroy { private teardown?: () => void; constructor(private readonly router: Router) { this.bootstrap(); } private async bootstrap() { await configure({ appId: environment.linkMeAppId, appKey: environment.linkMeAppReadKey, }); const initial = await resolveFromUrl(); if (initial?.path) { void this.router.navigateByUrl(initial.path); } this.teardown = onLink((payload) => { if (payload?.path) void this.router.navigateByUrl(payload.path); }).remove; } ngOnDestroy() { this.teardown?.(); } } Inject LinkMeService in your root component (e.g. AppComponent) to activate the SDK. API Reference configure(config) Initialize the SDK with your configuration. await configure({ appId: '<APP_ID>', appKey: '<APP_KEY>', autoResolve: true, // Optional (default: true in browser) autoListen: true, // Optional (default: true in browser) stripCid: true, // Optional (default: true) sendDeviceInfo: true, // Optional (default: true) resolveUniversalLinks: true, // Optional (default: true) fetch: customFetch, // Optional (default: globalThis.fetch) }); resolveFromUrl(url?) Resolve a link from the current URL (or a provided URL). const payload = await resolveFromUrl(); // or const payload = await resolveFromUrl('https://example.com?cid=abc123'); handleLink(url) Manually resolve an arbitrary URL string. const payload = await handleLink('https://example.com?cid=abc123'); onLink(callback) Listen for deep link payloads. const subscription = onLink((payload: LinkMePayload) => { console.log('Link received:', payload); // Navigate based on payload.path }); // Clean up when done subscription.remove(); claimDeferredIfAvailable() Claim a deferred deep link (probabilistic fallback for first launch). const payload = await claimDeferredIfAvailable(); if (payload?.path) { router.push(payload.path); } track(event, properties?) Track custom events. await track('open', { screen: 'home' }); await track('purchase', { amount: 99.99, currency: 'USD' }); setUserId(userId) Associate a user ID with the session. setUserId('user-123'); getLastPayload() Get the most recent resolved payload (if any). const lastPayload = getLastPayload(); Payload Structure type LinkMePayload = { linkId?: string; // Unique link identifier path?: string; // Deep link path (e.g., "profile") params?: Record<string, string>; // Query parameters utm?: Record<string, string>; // UTM parameters custom?: Record<string, string>; // Custom data cid?: string; // Click token identifier duplicate?: boolean; // Whether this was a duplicate claim }; Configuration options | Option | Type | Default | Description | | --- | --- | --- | --- | | appId | string | undefined | Optional app identifier. Sent via x-app-id header. | | appKey | string | undefined | Optional app key. Use a read-only key when enforced. | | fetch | typeof fetch | globalThis.fetch | Provide your own fetch for environments that need a polyfill. | | autoResolve | boolean | true in the browser | Resolve window.location.href immediately after configure. | | autoListen | boolean | true in the browser | Subscribe to popstate/hashchange to pick up navigation changes. | | stripCid | boolean | true | Removes cid from the address bar after it has been resolved. | | sendDeviceInfo | boolean | true | Sends basic locale/UA/screen metadata when resolving links. | | resolveUniversalLinks | boolean | true | POSTs to /api/deeplink/resolve-url when no cid is present but the URL matches your domain. | Optional: Class-based API For dependency injection or testing, use the class-based API: import { LinkMeWebClient } from '@li-nk.me/web-sdk'; const client = new LinkMeWebClient(); await client.configure({ appId: '<APP_ID>', appKey: '<APP_KEY>', }); const initial = await client.resolveFromUrl(); const sub = client.onLink((payload) => {}); await client.claimDeferredIfAvailable(); client.setUserId('user-123'); await client.track('open', { screen: 'home' }); sub.remove(); Security notes - When keys are required, issue a read-only key for browser usage. The SDK only calls read endpoints (GET /api/deeplink, POST /api/deeplink/resolve-url, POST /api/deferred/claim, POST /api/app-events). - Consider rate limiting public usage if you expose the SDK on public marketing pages. - The SDK strips cid parameters from the address bar after they are resolved to avoid leaking tokens via referrers. Troubleshooting - Verify cid query parameters make it through your router. If the parameter is removed before configure, pass the full URL into resolveFromUrl(urlString). - Use the browser devtools network tab to inspect calls to /api/deeplink and /api/deferred/claim. - If using SSR frameworks, ensure configure is only called on the client side (use useEffect or similar lifecycle hooks).

Last updated on Apr 04, 2026

React Native Setup Guide

React Native setup tutorial This walkthrough reproduces the Expo example located at sdks/react-native/example-expo so you can adapt it to your own navigation stack. Before you start - LinkMe app with iOS + Android identifiers filled in - Expo Router or React Navigation project running React Native 0.72+ (or Expo SDK 50+) - Node.js 18+ 1. Configure your LinkMe app 1. In the portal, open Apps → Your app. 2. Confirm the iOS bundle ID and Android package match what Expo/React Native will build. 3. Generate an API key and copy the appId and appKey. 4. Toggle Pasteboard for Deferred Links (iOS) if you install expo-clipboard. 2. Install packages npm install @li-nk.me/react-native-sdk npx expo install expo-clipboard # optional but recommended on iOS Package: @li-nk.me/react-native-sdk 3. Configure Expo / linking app.json { "expo": { "scheme": "yourapp", "plugins": [ [ "@li-nk.me/react-native-sdk/plugin/app.plugin.js", { "hosts": ["links.yourco.com"], "associatedDomains": ["links.yourco.com"], "schemes": ["yourapp"] } ] ], "ios": { "bundleIdentifier": "com.yourco.app" }, "android": { "package": "com.yourco.app" } } } Bare React Native projects should follow the native setup guides for Associated Domains (iOS) and App Links (Android) before continuing. 4. Initialize LinkMe in your root layout import { useEffect } from 'react'; import { useRouter } from 'expo-router'; import { configure, getInitialLink, claimDeferredIfAvailable, onLink, track, } from '@li-nk.me/react-native-sdk'; export function useLinkMe() { const router = useRouter(); useEffect(() => { let unsubscribe: { remove: () => void } | null = null; (async () => { await configure({ baseUrl: 'https://links.yourco.com', appId: process.env.EXPO_PUBLIC_LINKME_APP_ID!, appKey: process.env.EXPO_PUBLIC_LINKME_APP_KEY, debug: __DEV__, }); unsubscribe = onLink((payload) => { if (payload?.path) router.replace(payload.path as never); }); const initial = await getInitialLink(); if (initial?.path) { router.replace(initial.path as never); } else { const deferred = await claimDeferredIfAvailable(); if (deferred?.path) router.replace(deferred.path as never); } await track('open'); })(); return () => unsubscribe?.remove(); }, [router]); } Call useLinkMe() from app/_layout.tsx (Expo Router) or inside your root navigator. If you prefer React Navigation, wire the same hook inside your navigation container and use navigation.navigate(payload.path). 5. Wire platform-specific extras - iOS: Install expo-clipboard and enable the pasteboard toggle in the portal for deterministic first-install claims. Apple will show a one-time “pasted from…” banner—this is expected. - Android: No extra modules needed; the SDK uses the Play Install Referrer automatically. Ensure your release keystore fingerprint is saved in LinkMe so App Links verify. 6. Run the sample app Clone or open sdks/react-native/example-expo, copy .env.example to .env, set your keys, and run npx expo start. The example demonstrates onboarding flows, analytics tracking, and both direct/deferred routing. Next steps - Consult the Android and iOS SDK references for every method signature. - Read the Deferred Deep Linking Guide to understand how the JS SDK mirrors the native behavior. // Example: Map to Firebase Analytics // analytics().logEvent('campaign_details', { // source: payload.utm.source, // medium: payload.utm.medium, // campaign: payload.utm.campaign, // term: payload.utm.term, // content: payload.utm.content, // }); } } Deep Linking Configuration The Expo config plugin automatically configures: - iOS: Associated Domains (applinks:<your-domain>) and optional custom URL schemes - Android: HTTPS App Links intent filters and optional custom schemes No manual native configuration required.

Last updated on Apr 04, 2026

Flutter Setup Guide

Flutter setup tutorial Ship the same implementation as the /example app in the Flutter plugin. Before you start - LinkMe app configured with your iOS bundle ID and Android package name - Flutter 3.22+ - Access to update iOS entitlements and Android manifest 1. Create keys 1. Portal → Apps → Your app. 2. Generate (or reuse) an API key; copy the appId and appKey. 3. Enable pasteboard support for iOS if you want deterministic deferred links. 2. Install the package flutter pub add flutter_linkme_sdk Latest release: flutter_linkme_sdk 0.2.8. This package wraps LinkMeKit 0.2.8 (iOS/macOS) and the Android SDK 0.2.8. The example project lives at sdks/flutter/example. Open it in VS Code or Android Studio to compare with your implementation. 3. Configure platforms - iOS: In Xcode, enable Associated Domains and add applinks:links.yourco.com. LinkMe hosts the AASA file automatically. - Android: In AndroidManifest.xml, add an intent filter with android:autoVerify="true" for https://links.yourco.com. (You can also add a custom scheme.) Manual deep-link config mapping Use these values for your Flutter deep-link setup: { "hosts": ["links.yourco.com"], "associatedDomains": ["links.yourco.com"], "schemes": ["yourapp"] } What each field does and why it must be set: - hosts: your HTTPS deep-link domain(s). These map to iOS Associated Domains and Android App Links hosts. - associatedDomains: domain allowlist for iOS universal links; keep this aligned with your HTTPS hosts. - schemes: fallback custom URL scheme(s) for explicit scheme opens. Why this is required: - Without hosts / associatedDomains, universal links cannot be verified/routed into the iOS app. - Without hosts on Android, HTTPS links will not resolve as verified App Links. - Without schemes, yourapp://... style links will not open your app. - If host/scheme values are missing or mismatched, links fall back to browser or fail to route. add the same values manually in Flutter native targets: - hosts / associatedDomains: - iOS Runner target → Signing & Capabilities → Associated Domains: applinks:links.yourco.com - Android android/app/src/main/AndroidManifest.xml in your launch activity: <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="links.yourco.com" /> </intent-filter> - schemes: - iOS Info.plist URL types (CFBundleURLSchemes) add yourapp - Android custom scheme intent filter: <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="yourapp" /> </intent-filter> 4. Initialize LinkMe in main.dart import 'package:flutter/material.dart'; import 'package:flutter_linkme_sdk/flutter_linkme_sdk.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await LinkMe.shared.configure( const LinkMeConfig( baseUrl: 'https://links.yourco.com', appId: String.fromEnvironment('LINKME_APP_ID'), appKey: String.fromEnvironment('LINKME_APP_KEY'), ), ); final initial = await LinkMe.shared.getInitialLink(); final deferred = initial ?? await LinkMe.shared.claimDeferredIfAvailable(); runApp(App(initialPayload: initial, deferredPayload: deferred)); } class App extends StatefulWidget { const App({super.key, this.initialPayload, this.deferredPayload}); final LinkMePayload? initialPayload; final LinkMePayload? deferredPayload; @override State<App> createState() => _AppState(); } class _AppState extends State<App> { late final StreamSubscription<LinkMePayload> _sub; @override void initState() { super.initState(); _sub = LinkMe.shared.onLink.listen(routeUser); } @override void dispose() { _sub.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( home: HomeScreen( initial: widget.initialPayload, deferred: widget.deferredPayload, ), ); } void routeUser(LinkMePayload payload) { // push to Navigator based on payload.path/params } } Call LinkMe.shared.track('open') after key app events and LinkMe.shared.setAdvertisingConsent(true) once the user accepts your consent flow. 5. Test with the plugin example To replicate the shipped sample: 1. cd sdks/flutter/example 2. cp .env.example .env and fill in your keys (or pass via --dart-define). 3. flutter run on iOS + Android devices, then click one of your LinkMe slugs. Next steps - Use the Flutter-compatible iOS and Android references for method details. - Read the Deferred Deep Linking Guide for first-install attribution behavior.

Last updated on Apr 04, 2026

Node.js Setup Guide

Node.js setup tutorial Automate link creation and reporting from your backend or scripts. Before you start - LinkMe workspace with at least one app - Server/API key generated in the portal (write scope) 1. Install the package npm install @li-nk.me/node-sdk Package: @li-nk.me/node-sdk 2. Instantiate the client import LinkMeClient from '@li-nk.me/node-sdk'; const linkme = new LinkMeClient({ apiKey: process.env.LINKME_SERVER_KEY, // optional for read-only calls }); CommonJS usage const LinkMeClient = require('@li-nk.me/node-sdk').default; 3. Manage links programmatically // List links for an app const links = await linkme.listLinks('app_123'); // Create a marketing slug await linkme.createLink({ appId: 'app_123', slug: 'spring', deepLink: '/promo/spring', redirects: { iosStoreUrl: 'https://apps.apple.com/...', androidStoreUrl: 'https://play.google.com/...', webFallbackUrl: 'https://example.com/spring', forceRedirectWeb: false, }, }); // Update attributes later await linkme.updateLink('spring', { metadata: { campaign: 'spring-2025' }, }); // Delete if needed await linkme.deleteLink('spring'); All methods return typed responses; check their definitions in node_modules/@li-nk.me/node-sdk/dist/index.d.ts or the SDK reference. 4. Explore the source This monorepo already includes the SDK source under sdks/node. Inspect src/linkService.ts to see each REST call and adapt it for custom tooling. Next steps - Reference the generated TypeScript types (dist/index.d.ts) for the complete API surface. - Pair Node automation with the REST Endpoints & OpenAPI reference to build dashboards or QA workflows.

Last updated on Apr 04, 2026

iOS Setup Guide

iOS setup tutorial Use this walkthrough to integrate the LinkMe iOS SDK into a SwiftUI or UIKit app. The goal is to reproduce the ExampleApp that ships in the SDK repo, then adapt it to your product. Before you start - A LinkMe account with at least one workspace - An iOS bundle ID + associated team identifier - A LinkMe app configured with your bundle ID and (optionally) a branded domain 1. Create an app & grab keys 1. Open the LinkMe portal → Apps → Create app. 2. Enter the app name, bundle identifier, and (if needed) Apple Team ID. 3. In App Settings → API Keys, generate an app key. Copy both appId and appKey—you will paste them into your code. 4. Still in App Settings, toggle Pasteboard for Deferred Links if you want deterministic first-install attribution. 2. Install LinkMeKit Swift Package Manager 1. In Xcode choose File → Add Packages…. 2. Paste https://github.com/r-dev-limited/li-nk.me-ios-sdk and select tag v0.2.8. 3. Add LinkMeKit to your main app target. 4. Optional: open the sample found in the repo (ExampleApp folder) to see a complete implementation. CocoaPods Add LinkMeKit to your Podfile and run pod install: pod 'LinkMeKit', '~> 0.2.8' Use the latest published LinkMeKit pod matching the current SDK release tag (0.2.8 at time of writing). 3. Configure the SDK Initialize LinkMe as soon as your app launches. Point baseUrl at either your branded domain or https://li-nk.me. import LinkMeKit @main struct DemoApp: App { init() { LinkMe.shared.configure(config: .init( baseUrl: URL(string: "https://links.yourco.com")!, appId: ProcessInfo.processInfo.environment["LINKME_APP_ID"]!, appKey: ProcessInfo.processInfo.environment["LINKME_APP_KEY"], sendDeviceInfo: true, includeVendorId: true, includeAdvertisingId: false, debug: _isDebugAssertConfiguration() )) } var body: some Scene { WindowGroup { ContentView() } } } Enable Associated Domains in your target capabilities and add applinks:links.yourco.com. LinkMe hosts the AASA file automatically once your domain is connected. Required: This setup is mandatory for LinkMe on iOS. If Associated Domains and URL schemes are not configured, deep links will not open your app correctly. Manual deep-link config mapping Use these values for your iOS deep-link setup: { "hosts": ["links.yourco.com"], "associatedDomains": ["links.yourco.com"], "schemes": ["yourapp"] } What each field does and why it must be set: - hosts: your HTTPS deep-link domain(s). iOS uses this domain for universal links. - associatedDomains: domain allowlist for iOS universal links. This must match your Associated Domains entitlement. - schemes: fallback custom URL scheme(s) used when universal links are unavailable or for explicit scheme opens. Why this is required: - Without hosts / associatedDomains, iOS cannot validate your app for universal links on that domain. - Without schemes, scheme-based opens (for example yourapp://...) will not resolve into your app. - If these are missing or mismatched, links open in Safari or fail to route as expected. add the same values manually in iOS: - hosts / associatedDomains → Xcode Signing & Capabilities → Associated Domains: applinks:links.yourco.com - schemes → Info.plist URL types: <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>yourapp</string> </array> </dict> </array> 4. Handle direct and deferred links Connect LinkMe to your SwiftUI or UIKit lifecycle. struct ContentView: View { @State private var message = "Waiting for link" var body: some View { Text(message) .onOpenURL { url in LinkMe.shared.handle(url: url) } .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in LinkMe.shared.handle(userActivity: activity) } .task { LinkMe.shared.getInitialLink { initial in if let initial { message = "Opened via \(initial.path)" } else { LinkMe.shared.claimDeferredIfAvailable { deferred in if let deferred { message = "Deferred match \(deferred.path)" } } } } } } } Add a listener if you expect multiple incoming links while the app stays in memory: let unsubscribe = LinkMe.shared.addListener { payload in routeUser(to: payload) } Track lifecycle events when helpful: LinkMe.shared.track(event: "open") LinkMe.shared.setAdvertisingConsent(true) // after ATT / consent UI 5. Test with the example app The SDK repo ships with /ExampleApp, a runnable SwiftUI sample. Update ExampleApp/Configuration/linkme.env with your keys, run on device/simulator, and click a LinkMe short link to verify direct and deferred flows. Use this app as a reference when wiring your production code. Next steps - Dive into the iOS SDK reference for every method and payload shape. - Review the Deferred Deep Linking Guide for advanced install attribution tips.

Last updated on Apr 04, 2026