Skip to content

Commit 8d6f646

Browse files
author
WarpLink
committed
fix: send fingerprint_version on referrer attribution path
The Play Install Referrer (deterministic) attribution request omitted fingerprint_version, which the attribution API requires on every request. The request was rejected with a 400 validation error, so referrer-based re-engagement matches never landed. buildAttributionBody now sends fingerprint_version "basic" on the referrer branch. Adds a regression test for the referrer and enriched body shapes, derives the FingerprintCollector User-Agent assertion from SDK_VERSION, and bumps SDK_VERSION to 0.1.1.
1 parent e56eeef commit 8d6f646

5 files changed

Lines changed: 54 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [0.1.0] - Unreleased
8+
## [Unreleased]
9+
10+
### Fixed
11+
12+
- Referrer-based (Play Install Referrer) install attribution now sends the
13+
required `fingerprint_version` field. The deterministic referrer path
14+
previously omitted it, so the attribution API rejected the request with a
15+
400 validation error and re-engagement matches never landed.
16+
17+
## [0.1.0] - 2026-05-16
918

1019
### Added
1120

sdk/src/main/kotlin/app/warplink/WarpLink.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import app.warplink.internal.performDeferredCheck
1313

1414
object WarpLink {
1515

16-
const val SDK_VERSION = "0.1.0"
16+
const val SDK_VERSION = "0.1.1"
1717

1818
private val lock = Any()
1919
private var apiKey: String? = null

sdk/src/main/kotlin/app/warplink/internal/ApiClient.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ internal class ApiClient(
188188
)
189189
}
190190

191-
private fun buildAttributionBody(
191+
// internal (not private) so unit tests can assert the wire-body contract.
192+
internal fun buildAttributionBody(
192193
signals: DeviceSignals?,
193194
sdkVersion: String,
194195
deviceId: String?,
@@ -202,6 +203,9 @@ internal class ApiClient(
202203
}
203204
if (referrer != null) {
204205
body.put("referrer", referrer)
206+
// Schema requires fingerprint_version on every request; the
207+
// deterministic referrer path carries no enriched signals.
208+
body.put("fingerprint_version", "basic")
205209
return body
206210
}
207211
if (signals != null) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package app.warplink.internal
2+
3+
import org.junit.Test
4+
import org.junit.runner.RunWith
5+
import org.robolectric.RobolectricTestRunner
6+
import kotlin.test.assertEquals
7+
8+
/**
9+
* The server's attributionMatchSchema requires fingerprint_version on every
10+
* POST /attribution/match request, including the deterministic Play Install
11+
* Referrer path (it has no enriched signals but must still send the field, or
12+
* the API rejects the request with a 400 validation error).
13+
*/
14+
@RunWith(RobolectricTestRunner::class)
15+
class ApiClientAttributionBodyTest {
16+
17+
private val client = ApiClient(
18+
"wl_test_abcdefghijklmnopqrstuvwxyz012345",
19+
"http://localhost:1"
20+
)
21+
private val linkId = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
22+
23+
@Test
24+
fun `referrer body includes required fingerprint_version`() {
25+
val body = client.buildAttributionBody(null, "0.1.0", null, linkId)
26+
assertEquals(linkId, body.getString("referrer"))
27+
assertEquals("basic", body.getString("fingerprint_version"))
28+
}
29+
30+
@Test
31+
fun `enriched signals body includes fingerprint_version`() {
32+
val signals = DeviceSignals("en-US", 1080, 1920, -300, "ua")
33+
val body = client.buildAttributionBody(signals, "0.1.0", null, null)
34+
assertEquals("enriched", body.getString("fingerprint_version"))
35+
}
36+
}

sdk/src/test/kotlin/app/warplink/internal/FingerprintCollectorTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package app.warplink.internal
22

33
import androidx.test.core.app.ApplicationProvider
4+
import app.warplink.WarpLink
45
import org.junit.Test
56
import org.junit.runner.RunWith
67
import org.robolectric.RobolectricTestRunner
@@ -63,6 +64,6 @@ class FingerprintCollectorTest {
6364
)
6465
var signals: DeviceSignals? = null
6566
collector.collectFingerprint { r -> signals = r.getOrNull() }
66-
assertEquals("WarpLink-Android/0.1.0", signals!!.userAgent)
67+
assertEquals("WarpLink-Android/${WarpLink.SDK_VERSION}", signals!!.userAgent)
6768
}
6869
}

0 commit comments

Comments
 (0)