SDK Specification
Last updated: May 1, 2026
This document defines what every LinkForty SDK must implement. It is the canonical reference for contributors building SDKs in new languages or platforms.
The full specification is maintained in the Core repository. This page provides the same content in a browsable format.
We welcome community SDKs for any platform. If you're building one, open an issue on GitHub to coordinate with the team and avoid duplicate work.
Overview
A LinkForty SDK must support 13 feature areas:
| # | Feature | Description |
|---|---|---|
| 1 | Initialization | SDK setup, first launch detection |
| 2 | Deferred Deep Linking | Match installs to prior link clicks |
| 3 | Direct Deep Linking | Handle links when app is already installed |
| 4 | Server-Side URL Resolution | Resolve Universal/App Links server-side |
| 5 | Event Tracking | In-app conversion events |
| 6 | Revenue Tracking | Structured purchase tracking |
| 7 | Link Creation | Create short links from the app |
| 8 | User Identity | Associate a user ID with SDK operations |
| 9 | Attribution Data Access | Read cached attribution data locally |
| 10 | Data Management | Clear data and reset SDK state |
| 11 | Configuration | Required and optional settings |
| 12 | Error Handling | Expected failure behaviors |
| 13 | Offline Resilience | Event queuing and retry |
1. Initialization
The SDK must be initialized before any other functionality is used. Initialization performs these steps:
- Validate configuration (base URL, attribution window bounds, HTTPS enforcement)
- Set up internal components (networking, storage, fingerprint collection, deep link handling)
- Detect first launch and persist that state
- On first launch: collect a device fingerprint and report the install to the server
- On subsequent launches: load cached attribution data from local storage
Behavioral requirements
- Initialization must be idempotent or guard against double-init
- Methods that require initialization must fail clearly if called before init
- The SDK should be usable as a singleton or single shared instance
2. Deferred Deep Linking
Matches a new app install back to the link click that drove it, using probabilistic device fingerprinting.
What the SDK must do
- On first launch, collect a device fingerprint containing:
- User agent string
- Timezone identifier
- Device language
- Screen dimensions (native pixels)
- Platform (
iOS/Android) - Platform version
- App version
- Optional device ID (IDFA/GAID, only if user consented)
- Send the fingerprint to the server
- Receive an attribution response containing: install ID, whether attributed, confidence score, matched factors, and deep link data (if attributed)
- Persist the install ID and deep link data locally
- Deliver the result to a registered callback (or null/nil for organic installs)
- Allow the callback to be registered before or after initialization (if registered after and data is already available, invoke immediately)
API endpoint: POST /api/sdk/v1/install
App token (Cloud only)
The SDK should accept an optional appToken during initialization and include it in the /api/sdk/v1/install request body. The app token is a public, workspace-scoped identifier that lets LinkForty Cloud attribute installs to the right workspace — including organic (unattributed) installs that wouldn't otherwise be tied to any workspace.
- Where to find it: Dashboard → Workspace Settings → App Token
- Format:
at_<32 hex chars> - Safety: The token is public — designed to ship inside your app bundle. It only identifies which workspace owns the install; it cannot authenticate API actions or expose private data.
- Self-hosted (Core): The endpoint accepts the field but ignores it. Self-hosted single-tenant deployments don't need it.
- Without a token: Attributed installs (deeplink click → app open) still work via the existing fingerprint matcher. Organic installs (App Store discovery, social mentions, etc.) won't be visible in your workspace's analytics until the token is configured.
Example request body:
{
"userAgent": "...",
"platform": "ios",
"platformVersion": "17.4",
"deviceId": "...",
"appToken": "at_a1b2c3d4e5f6....",
"sdkName": "ios",
"sdkVersion": "1.4.0"
}
Every install and event request should also include sdkName (one of ios, android, flutter, react-native, expo) and sdkVersion (the SDK's own semantic version). These let LinkForty report which SDKs and versions are in use and flag outdated integrations. Both are optional and backward-compatible — older SDKs that omit them simply store null.
3. Direct Deep Linking
Handles the case where a user taps a LinkForty link and the app is already installed. The OS opens the app directly (via Universal Links on iOS, App Links on Android, or custom URL schemes).
What the SDK must do
- Accept an incoming URL from the app's deep link handler
- Parse the URL to extract the short code, UTM parameters, and custom query parameters
- If the SDK is configured with a server connection, resolve the URL server-side for enriched data
- Deliver the URL and parsed data to a registered callback
- Support multiple callback registrations
Platform notes
- iOS: The app must manually pass the URL to the SDK (from
onOpenURLorAppDelegate) - Android: Same -- the app passes the intent URL to the SDK
- React Native / Expo: The SDK may automatically listen via the platform's linking API, but must still support explicit URL handling
4. Server-Side URL Resolution
When a Universal Link or App Link bypasses the redirect server (the OS opens the app directly), the SDK must resolve the link server-side to get the full link metadata.
What the SDK must do
- Extract path segments from the URL to determine the resolve path:
- Single segment:
/api/sdk/v1/resolve/{shortCode} - Two segments:
/api/sdk/v1/resolve/{templateSlug}/{shortCode}
- Single segment:
- Collect a device fingerprint and send it as query parameters (
fp_tz,fp_lang,fp_sw,fp_sh,fp_platform,fp_pv) - Return the enriched deep link data from the server response
- Fall back to local URL parsing if the server request fails (network error, timeout, etc.)
5. Event Tracking
Tracks in-app events for conversion attribution. Events are tied to the install ID so the server can correlate them with the original link click.
What the SDK must do
- Accept an event name and optional properties dictionary
- Send the event to the server with the install ID and timestamp
- Stamp the last-click attribution context on every event (see below)
- Include
sdkNameandsdkVersion(as on the install request) - Handle failures gracefully (see Offline Resilience)
API endpoint: POST /api/sdk/v1/event
Last-click attribution
Every event should be credited to the deep link that most recently opened the app — a deferred install open or a direct re-engagement open — so the server can attribute in-app activity to the link that drove it. The SDK keeps an "active attribution context" that the newest deep-link open supersedes, persists it across app restarts, and stamps these fields onto every event:
| Field | Description |
|---|---|
attributedLinkId | The link currently credited (last-click). Omitted for organic activity (no deep link has opened the app). |
attributedClickId | The originating click id, when known. |
linkOpenedAt | ISO 8601 timestamp of when that deep link opened the app. |
sessionId | A per-app-open session id (generated on cold start, rotated on each new deep-link open) used to group a visit's screens. |
The conversion window (how long a stamped event still counts) and session grouping are applied server-side at query time — the SDK only reports the active link, when it opened, and the current session.
Example request body:
{
"installId": "...",
"eventName": "add_to_cart",
"eventData": { "productId": "123" },
"timestamp": "2026-06-11T12:00:00.000Z",
"sdkName": "ios",
"sdkVersion": "1.4.0",
"attributedLinkId": "...",
"attributedClickId": "...",
"linkOpenedAt": "2026-06-11T11:58:00.000Z",
"sessionId": "..."
}
Screen views
Screen views are reported as a regular event named screen_view, with the screen name in eventData.screen (and the previously tracked screen in eventData.previousScreen when known). They flow through the same pipeline and carry the same attribution stamp, which powers the per-link screen-flow funnel.
6. Revenue Tracking
A convenience for tracking revenue-specific events with structured amount and currency fields. The Events dashboard aggregates revenue using this convention.
What the SDK must do
- Provide a
trackRevenue(amount, currency, properties?)method - Validate that amount is non-negative
- Send as an event via
trackEventwith these exact fields:- Event name:
revenue - Event data:
{ revenue: <amount as number>, currency: <ISO 4217 code>, ...properties }
- Event name:
Convention (all SDKs must follow this exactly)
| Field | Value | Example |
|---|---|---|
| Event name | "revenue" | — |
eventData.revenue | Numeric amount (double/float) | 29.99 |
eventData.currency | ISO 4217 currency code | "USD" |
The Events dashboard computes total revenue as SUM(event_data->>'revenue') WHERE event_name = 'revenue'. If an SDK uses a different event name or field name, revenue will not be aggregated correctly.
7. Programmatic Link Creation
Allows apps to create short links on behalf of the user (e.g., for sharing content).
What the SDK must do
- Accept link creation options including an optional template ID, deep link parameters, title, description, custom code, UTM parameters, and external user ID
- Require an API key to be configured
- Send to
POST /api/sdk/v1/linkswith an optionaltemplateIdin the request body:- With
templateId: Uses the specified template (must belong to the organization; falls back to default if not found) - Without
templateId: Uses the organization's default template (is_default = true), or the most recently created template if no default is set
- With
- Return the created link's URL, short code, link ID, and deduplication status
- If an SDK-level external user ID is set (see User Identity), include it in the request body unless overridden per-call
This feature requires an API key. SDKs must fail clearly if no API key is configured.
8. User Identity
Allows apps to associate an external user identifier with SDK operations, primarily link creation. This enables per-user deduplication and share attribution on the dashboard.
What the SDK must do
- Provide a method to set an external user ID (any string: UUID, email, integer, etc.)
- Provide a method to get the current external user ID
- Passing null/nil clears the stored ID
- The stored ID is automatically attached to all
createLink()calls unless overridden per-call viaCreateLinkOptions.externalUserId - The ID is stored in memory only (not persisted to disk)
- Clearing SDK data or resetting the SDK also clears the external user ID
Behavioral requirements
- The external user ID does not require initialization — it can be set before or after
initialize() - Per-call
externalUserIdinCreateLinkOptionstakes precedence over the SDK-level value - The value is not sent to any endpoint automatically — it is only included in link creation requests
9. Attribution Data Access
The SDK must provide access to cached attribution data from local storage without requiring a network call.
What the SDK must expose
- Install ID -- the server-assigned UUID for this install
- Install data -- the deep link data from attribution (null if organic)
- First launch status -- whether this is the first launch of the app
10. Data Management
What the SDK must support
- Clear data -- wipe all locally stored SDK data (install ID, attribution data, cached deep links, event queue). Used for testing and GDPR compliance.
- Reset -- return the SDK to an uninitialized state so it can be re-initialized. This is separate from clearing data.
11. Configuration
Required configuration
| Field | Description |
|---|---|
| Base URL | The LinkForty server URL (e.g., https://go.yourdomain.com) |
Optional configuration
| Field | Description | Default |
|---|---|---|
| API key | Required for link creation and Cloud features | None |
| App token | Public workspace identifier for Cloud install scoping (format: at_<32 hex>). See Section 2 → App token. Self-hosted Core ignores this field. | None |
| Debug mode | Enable verbose logging | Off |
| Attribution window | How far back to match installs to clicks | 7 days (168 hours) |
Validation requirements
- Base URL must be HTTPS (except
localhost/127.0.0.1for local development) - Attribution window must be between 1 hour and 2160 hours (90 days)
12. Error Handling
SDKs must handle these error scenarios. The mechanism (typed enums, error codes, exceptions) is platform-specific.
| Scenario | Expected behavior |
|---|---|
| Method called before initialization | Throw/return an error |
| Double initialization | Warn or throw |
| Network failure during install report | Treat as organic install, deliver null to callback |
| Network failure during URL resolution | Fall back to local URL parsing |
| Network failure during event tracking | Queue the event for retry |
| Link creation without API key | Throw/return an error |
| Server returns error response | Surface the error to the caller |
| Invalid configuration | Throw/return an error during initialization |
13. Offline Resilience
Event queue
Events should be queued locally when the network is unavailable and retried when connectivity is restored.
- Maximum queue size: 100 events
- Queue must persist across app restarts (local storage)
- Provide a way to manually flush the queue
- Provide a way to clear the queue without sending
- Provide a way to check the queue size
Other operations
- Install reporting: if the network call fails on first launch, treat as organic. The install can be re-attributed on a subsequent launch if the SDK detects it hasn't successfully reported yet.
- URL resolution: fall back to local parsing (never block the deep link flow on a network call)
API Endpoints Reference
All endpoints are relative to the configured base URL.
Core endpoints (available in both self-hosted Core and Cloud)
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /api/sdk/v1/install | None | Report install, get deferred deep link. Body accepts optional appToken for Cloud workspace scoping. |
GET | /api/sdk/v1/resolve/:shortCode | None | Resolve link without redirect |
GET | /api/sdk/v1/resolve/:templateSlug/:shortCode | None | Resolve template link without redirect |
POST | /api/sdk/v1/event | None | Track in-app events |
GET | /api/sdk/v1/health | None | Health check |
Cloud-only endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /api/sdk/v1/links | API key | Create link (optional templateId; uses default template if omitted) |
Authentication
When an API key is configured, send it as: Authorization: Bearer <api_key>
Data Models
Canonical field names for cross-SDK data models. SDKs should use platform-appropriate naming conventions (camelCase for Swift/Kotlin/JS/TS) but the data must map to these fields.
DeepLinkData
| Field | Type | Required | Description |
|---|---|---|---|
| shortCode | string | Yes | The link's short code |
| iosUrl | string | No | iOS destination URL |
| androidUrl | string | No | Android destination URL |
| webUrl | string | No | Web fallback URL |
| utmParameters | UTMParameters | No | UTM tracking parameters |
| customParameters | map | No | Custom query parameters |
| deepLinkPath | string | No | In-app routing path (e.g., /product/123) |
| appScheme | string | No | App URI scheme (e.g., myapp) |
| clickedAt | datetime | No | When the link was clicked (ISO 8601) |
| linkId | string | No | Link UUID from the backend |
InstallResponse
| Field | Type | Required | Description |
|---|---|---|---|
| installId | string | Yes | Server-assigned install UUID |
| attributed | boolean | Yes | Whether install was matched to a click |
| confidenceScore | number | Yes | Match confidence (0-100) |
| matchedFactors | string[] | Yes | Which fingerprint factors matched |
| deepLinkData | DeepLinkData | No | Link data if attributed (null if organic) |
CreateLinkOptions
| Field | Type | Required | Description |
|---|---|---|---|
| templateId | string | No | Template UUID (uses organization's default template if omitted or not found) |
| templateSlug | string | No | Template slug (for URL construction) |
| deepLinkParameters | map | No | In-app routing parameters |
| title | string | No | Link title |
| description | string | No | Link description |
| customCode | string | No | Custom short code |
| utmParameters | UTMParameters | No | Campaign tracking parameters |
| externalUserId | string | No | Identifier for the app user creating the link (enables per-user deduplication and share attribution) |
CreateLinkResult
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Full shareable URL |
| shortCode | string | Yes | Generated short code |
| linkId | string | Yes | Link UUID |
| deduplicated | boolean | No | True if an existing link was returned instead of creating a new one |
UTMParameters
| Field | Type | Required |
|---|---|---|
| source | string | No |
| medium | string | No |
| campaign | string | No |
| term | string | No |
| content | string | No |
DeviceFingerprint
| Field | Type | Required | Description |
|---|---|---|---|
| userAgent | string | Yes | App/version + OS/version |
| timezone | string | No | IANA timezone identifier |
| language | string | No | Device language/locale |
| screenWidth | number | No | Native screen width in pixels |
| screenHeight | number | No | Native screen height in pixels |
| platform | string | No | iOS, Android, etc. |
| platformVersion | string | No | OS version string |
| appVersion | string | No | Host app version |
| deviceId | string | No | IDFA/GAID (only if user consented) |
| attributionWindowHours | number | No | Attribution window in hours |
Feature Parity Matrix
Current status across existing SDKs. Update this table when adding features or SDKs.
| # | Feature | iOS | React Native | Android | Expo |
|---|---|---|---|---|---|
| 1 | Initialization | Done | Done | Done | Done |
| 2 | Deferred deep linking | Done | Done | Done | Done |
| 3 | Direct deep linking | Done | Done | Done | Done |
| 4 | Server-side URL resolution | Done | Done | Done | Done |
| 5 | Event tracking | Done | Done | Done | Done |
| 6 | Revenue tracking | Done | Done | Done | Done |
| 7 | Link creation | Done | Done | Done | Done |
| 8 | User identity | Done | Done | Done | Done |
| 9 | Attribution data access | Done | Partial | Done | Done |
| 10 | Data management | Done | Partial | Done | Done |
| 11 | Configuration validation | Done | Missing | Done | Done |
| 12 | Error handling | Done | Partial | Done | Done |
| 13 | Offline resilience | Done | Missing | Done | Done |
React Native parity notes
- Attribution data access:
isFirstLaunch()is private, not exposed to consumers - Data management: No
reset()method (onlyclearData()) - Error handling: Uses generic
Errorthrows instead of typed error cases - Configuration validation: No HTTPS enforcement or attribution window bounds checking
- Offline resilience: Events are fire-and-forget; no queue, no retry on failure
Getting Started as a Contributor
Building your SDK
- Read the React Native SDK source as a reference implementation
- Implement the 13 feature areas in order (initialization first)
- Use the API endpoints and data models as your contract
- Test against a local LinkForty Core instance (quick start)
Submission requirements
Before an SDK can be listed on the LinkForty docs, we verify it works end-to-end. Please provide:
- Repository link and intended package registry name (npm, pub.dev, Maven, CocoaPods, etc.)
- Example app or
/examplefolder demonstrating the core flows: initialization, deferred deep linking, direct deep linking, and link creation - CI pipeline with linting and tests (GitHub Actions or equivalent)
- Install and quickstart documentation in the README
How SDKs are listed
| Tier | Requirements | Listed as |
|---|---|---|
| Community | Passes end-to-end verification, meets submission requirements | "Community SDK" on the docs site with link to your repo |
| Official | Multiple stable releases, co-maintained with LinkForty team, transferred to the LinkForty GitHub org | "Official SDK" with full documentation on the docs site |
To get started, open an issue on GitHub to coordinate with the team and avoid duplicate work.