SDK Data Flow
For SDK 1.1.0: the fields each SDK sends on validate, resolve, and attribution match, what the server stores, redacted payloads, and URL redaction advice.
This guide lists every request the WarpLink SDKs make, the fields in each request, and what we store from it. It is written for SDK 1.1.0 on iOS, Android, React Native, and Flutter. React Native and Flutter call the native SDKs, so they send the same fields. A later SDK release can change a field. When it does, this page changes with it.
The SDK reads no advertising identifier (no IDFA and no Android advertising ID). It sends requests only to the API address in its apiEndpoint option, which defaults to https://api.warplink.app/v1. Retention periods are in the retention table on the privacy page.
The three requests
Every request carries Authorization: Bearer <SDK key> and a User-Agent of the form WarpLink-iOS/1.1.0 or WarpLink-Android/1.1.0. The server also sees the request's source IP address and the standard connection data every web request carries.
| Request | When | Purpose |
|---|---|---|
GET /v1/sdk/validate | At configure(), unless a recent successful result is cached | Check the key and fetch your verified link domains |
GET /v1/links/resolve/{slug} | When a link is tapped | Turn the tapped link into its destination and deep link |
POST /v1/attribution/match | Once at first launch, and again on a later launch if the first attempt got no definitive answer | Match the install to the link that led to it |
Validate
The request has no body and no query. The response carries three fields.
GET /v1/sdk/validate
Authorization: Bearer wl_live_<redacted>
User-Agent: WarpLink-iOS/1.1.0
{
"valid": true,
"org_id": "<organization id>",
"domains": ["aplnk.to", "acme.aplnk.to", "links.example.com"]
}
We store nothing from this request except the key's last-used time. Rate limiting and request logs see it as they see any other request.
Resolve
The SDK sends the slug in the path, the link's host in domain, and the query string of the tapped URL in params. It also sends a random identifier for the tap in a header. The identifier is created for each tap, repeated on every retry of that tap, and not tied to a person or a device.
GET /v1/links/resolve/summer-sale?domain=acme.aplnk.to¶ms=utm_source%3Demail
Authorization: Bearer wl_live_<redacted>
User-Agent: WarpLink-iOS/1.1.0
X-WarpLink-Tap-Id: <random uuid>
{
"id": "<link id>",
"slug": "summer-sale",
"domain": "acme.aplnk.to",
"destination_url": "https://example.com/sale",
"ios_url": "myapp://sale",
"ios_fallback_url": null,
"android_url": "myapp://sale",
"android_fallback_url": null,
"custom_params": {},
"created_at": "2026-10-01T00:00:00.000Z"
}
A resolve from an SDK key with an SDK user agent records one click. The click record holds:
| Field | Value |
|---|---|
link_id, org_id, clicked_at | The link, its organization, and the time |
ip_hash | A keyed hash of the source IP address. The address itself is not stored. |
country, region, city | Approximate location, derived from the IP address by the edge network |
device_type, os | mobile, and iOS or Android from the SDK user agent |
source | app_open |
| Click identifier | The tap identifier, so the retries of one tap count once |
The click record has no referrer, browser, or UTM values, and the params string is not stored in it. The params string is part of the request URL, so treat it as data that reaches our servers.
Attribution match
The SDK sends the device signals it needs to match an install to a tap. These are all of the fields.
iOS:
{
"platform": "ios",
"sdk_version": "1.1.0",
"fingerprint_version": "enriched_tz",
"accept_language": "en-CA",
"timezone_offset": 240,
"timezone": "America/Toronto",
"device_id": "<IDFV, a UUID>",
"app_bundle_id": "com.example.app",
"is_reinstall": false
}
Android:
{
"platform": "android",
"sdk_version": "1.1.0",
"fingerprint_version": "enriched_tz",
"accept_language": "en-CA",
"timezone_offset": 240,
"timezone": "America/Toronto",
"app_package_name": "com.example.app",
"referrer": "<link id read from the Play Install Referrer>",
"is_reinstall": false
}
| Field | What it is |
|---|---|
accept_language | The device's preferred language |
timezone_offset, timezone | The offset in minutes and the IANA zone name |
fingerprint_version | Which of the above the SDK could send: basic, enriched, or enriched_tz |
device_id | iOS only: the vendor identifier (IDFV). It is shared by every app from one developer account on a device. Android sends none. |
referrer | Android only: the link identifier from the Play Install Referrer, when one exists |
app_bundle_id, app_package_name | Which of your apps is asking |
is_reinstall | Whether this device had run the app before. On iOS it comes from a Keychain marker. |
sdk_version, platform | The SDK release and the operating system |
Like resolve, the request carries the X-WarpLink-Tap-Id header: a random identifier for one attribution attempt, repeated on its retries. The server stores it on the install record, so a retried request returns the same install, unless the request is a reinstall, in which case a new install can be created.
The SDK does not send the IP address, because it cannot know its own public address. The server reads the address from the request.
{
"matched": true,
"match_type": "probabilistic",
"match_confidence": 0.65,
"match_guaranteed": false,
"link_id": "<link id>",
"deep_link_url": "myapp://sale",
"destination_url": "https://example.com/sale",
"custom_params": {},
"install_id": "<install id>",
"app_id": "<app id>"
}
What we store from a match
| Record | Fields |
|---|---|
| Install | Link, organization, and app. Platform. device_fingerprint (a keyed hash, see below). match_type, match_confidence. Install and attribution times. device_id (the IDFV on iOS). The matched click identifier, the tap identifier, and is_reinstall. The routing the match answered with: destination, deep link, and custom parameters. |
| Attempt | Organization, app, platform, SDK version, which method ran, fingerprint_version, whether it matched, a failure reason, the number of candidate clicks, a coarse class of the IP address (cgnat, ipv6, or ipv4), and is_reinstall. An attempt record holds no IP address, no device identifier, and no fingerprint. |
| Deferred entry | Written at click time, not by the SDK: the fingerprint key, link, destination, deep link, custom parameters, and the entry's expiry. It lives only for the link's match window, 6 hours by default and 24 at most. |
The IP address is not stored in install, attempt, or click records.
The hashing key
The IP hash in a click and the fingerprint in an install are HMAC-SHA256 values. The key is a single server-side secret. The SDK never holds it, and no request or response carries it. The same key is used for all organizations, so the same IP address produces the same hash everywhere in our systems. A hash is a pseudonym. Anyone who knows the key and a candidate address can recompute it, so we treat hashes as personal data and keep them under the retention rules in the privacy page.
The fingerprint is built from the source IP address, the normalized language, and the time zone. It describes a network at a moment, not a person.
Keep personal data out of link URLs
The tapped URL is data that moves through several places. Its host and slug appear in the resolve path. Its query string goes to us in params. UTM values in the URL are stored in click records. The URL also appears in message logs, browser history, and referrer headers.
- Do not put an email address, phone number, user ID, name, or token in a link's slug, query string, UTM values, or destination.
- Use opaque identifiers that mean nothing outside your own systems, and look up the person on your server.
- Do not use custom parameters for secrets. They reach the app through the SDK and are stored with the install.
- A referral or invite link should carry a random code, not an account identifier.
- If you suspect that personal data went into a link, edit the link and ask us to delete the affected records at
privacy@warplink.app.
What stays on the device
The SDK keeps its own state on the device: the cached attribution result, completion markers, the cached link domains, and the time of the last key check. On iOS it also keeps a Keychain marker for is_reinstall. The iOS SDK ships a privacy manifest that declares the vendor identifier and is_reinstall, with tracking set to false. Use these lists when you complete your app store privacy answers.
Related pages: Abuse protection and threat model, Attribution accuracy, and the privacy policy.
Fallbacks When the API Is Unreachable
What the WarpLink SDKs do when the API cannot be reached, plus a fallback pattern with code for iOS, Android, React Native, and Flutter.
Changelog
What changed in WarpLink across the dashboard, API, MCP server, and the iOS, Android, React Native, and Flutter SDKs, with a dated entry for each release.