Skip to content

feat(attribution): report how certain an install match is - #18

Merged
onamfc merged 2 commits into
mainfrom
feat/match-certainty
Oct 21, 2026
Merged

onamfc merged 2 commits into
mainfrom
feat/match-certainty

Conversation

@onamfc

@onamfc onamfc commented Oct 21, 2026

Copy link
Copy Markdown
Member

What changed

  • InstallResponse gains attributionMethod: String? ("fingerprint" when matched to a click, "none" when organic) and competingLinks: Int?.
  • DeepLinkData gains confidenceScore: Double?, matchedFactors: List<String>?, attributionMethod: String? and competingLinks: Int?. They are set on the deferred deep link and null on a direct open (the resolve endpoint does not send them).
  • All new fields are nullable with defaults, so a response from a server that omits them decodes as before (Moshi codegen uses the default for a missing field).
  • DeepLinkData is stored as install data; the new fields round-trip through storage, and install data stored by an earlier SDK version decodes with them null.
  • On launches after the first, initialize() builds its InstallResponse from stored data; attributionMethod and competingLinks are left null there rather than guessed. The stored DeepLinkData keeps the values from the launch that reported the install.
  • Both constructors are @JvmOverloads with the new parameters last, so the previous Java constructor signatures (10-arg DeepLinkData, 5-arg InstallResponse) still exist. Kotlin code compiled against 1.6.x that constructs either class with default arguments, or calls copy(), must be recompiled; Kotlin source compiles unchanged.
  • KDoc, API.md, llms.txt and README.md document the fields, with a short competingLinks example in the deferred deep link section. llms.txt also lists confidenceScore as Double, which is its type.
  • Version 1.7.0 in gradle.properties, README.md and CHANGELOG.md.

Why

competingLinks tells an app whether other links were also in contention for the install. Above 0, a tap on another device on the same network may be what led to this install, so an app acting on a sensitive link (a sign-in, a gift) can treat the match as unverified, for example by asking the user to confirm. confidenceScore and matchedFactors were already sent inside deepLinkData and are now exposed to the deferred deep link callback.

Tests

  • New: decoding with and without the new fields for InstallResponse and its deepLinkData, an organic response with attributionMethod: "none", DeepLinkData defaults, a JSON round-trip with the new fields, decoding stored JSON without them, and the cached-launch InstallResponse leaving both fields null.
  • ./gradlew sdk:testDebugUnitTest sdk:lintDebug (the CI commands): 201 tests, 0 failures; lint clean.
  • Checked the compiled constructor signatures with javap.

InstallResponse carries attributionMethod ("fingerprint" or "none") and competingLinks, the number of other links that had a click from the install's network that also reached the confidence threshold. Above 0, a tap on another device on the same network may be what led to the install, so an app acting on a sensitive link can treat the match as unverified. Both are null from a server that does not send them, and on launches after the first, which return the stored attribution.

DeepLinkData carries confidenceScore, matchedFactors, attributionMethod and competingLinks on the deferred deep link. They are stored with the install data, so getInstallData() returns them on later launches; data stored by an earlier version decodes with them null, and a direct open leaves them null.

Both constructors are @jvmoverloads with the new parameters last, so the previous Java constructor signatures remain. Version 1.7.0.
@onamfc
onamfc merged commit 09383cd into main Oct 21, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants