What's new — the install waits for consent
Consent gating is now deny-by-default and hold-until-decision (LinkTrailOptions.requireConsent, on by default). Until your app records a decision, nothing leaves the device: no install call, no clipboard read, no device identifier. The held install — including a click token captured before the answer — is persisted, so a user who decides on a later launch is still attributed.
Each decision sends exactly one install:
| Answer | What goes out |
|---|---|
setConsent(true) |
the counted, attributed install — click token replayed if one was captured |
setConsent(false) |
one uncounted install with consent: false — the backend resolves the deferred link so the user still lands in the right place, but records nothing, and the request carries no deviceId and no Idempotency-Key |
A denial is sent rather than swallowed so withheld installs are countable instead of indistinguishable from installs that never happened — and it repeats on neither path: both answers have a persisted one-shot.
LinkTrailPasteButton grants consent
The user's deliberate tap on the system paste control is the affirmative act, so the install that follows carries consent: true — no separate prompt needed:
LinkTrailPasteButton() // tap = token + consent
LinkTrailPasteButton(grantsConsent: false) // a CMP owns consent; tap = token only⚠️ Migration note
If your app uses the defaults and never calls setConsent(_:), it now sends nothing at all — no install, no attribution, no deferred routing. That is the deny-by-default contract working as designed. Either call setConsent(_:) once your consent UI resolves, or opt out if consent is handled elsewhere:
LinkTrailOptions(requireConsent: false) // tracks at configure, flag never consultedInstall
.package(url: "https://github.com/linktrail-io/ios-sdk.git", from: "0.0.12")pod 'LinkTrailSDK', '~> 0.0.12'Verified with 93 unit tests and 12 Maestro end-to-end flows ({custom, subdomain} × {allow, deny} × {automatic, pasteButton, none}) against production and test backends.