Mobile apps (iOS and Android)
A one-file module, in Swift or Kotlin, that sends opens and conversions without any advertising id.
The request
POST
https://firstmrr.app/app/api/collectPublic key in the body (k)| Field | Type | Required | Description |
|---|---|---|---|
k | string | Required | Your public key. |
e | string | Required | The event: open or screen (counted as a visit), signup, activation, trial, payment, cancel, source, ab:exposure, or a name of your own. |
a | string | Required | The install id: a random UUID created at the first launch. |
p | string | Optional | The screen shown (/home). |
w | number | Optional | The screen width in points (tablet from 600). |
v / c / i | number / string / string | Optional | Payment: amount, currency, transaction id. |
s / d | string / string | Optional | Origin answer and account named. |
ab | object[] | Optional | A/B assignments, [{ "test": "pricing_page", "variant": "b" }], 4 at most. |
The answer is always 204, without a body: don't read it and don't retry. Send without blocking the UI, 5 seconds at most.
The module
import UIKit
enum FirstMRR {
private static let endpoint = URL(string: "https://firstmrr.app/app/api/collect")!
private static let key = "pk_0123456789abcdef01234567" // your public key
private static let defaults = UserDefaults.standard
/// A random id created at the first launch: never the IDFA or identifierForVendor.
static var installId: String {
if let id = defaults.string(forKey: "firstmrr.installId") { return id }
let id = UUID().uuidString
defaults.set(id, forKey: "firstmrr.installId")
return id
}
/// The "Audience measurement" setting of your app.
static var enabled: Bool {
get { !defaults.bool(forKey: "firstmrr.optOut") }
set { defaults.set(!newValue, forKey: "firstmrr.optOut") }
}
static func track(_ event: String, screen: String = "/", extra: [String: Any] = [:]) {
guard enabled else { return }
var body: [String: Any] = ["k": key, "e": event, "a": installId, "p": screen, "w": Int(UIScreen.main.bounds.width)]
body.merge(extra) { _, new in new }
guard let data = try? JSONSerialization.data(withJSONObject: body) else { return }
var request = URLRequest(url: endpoint, timeoutInterval: 5)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = data
URLSession.shared.dataTask(with: request).resume() // fire and forget: no retry, no queue
}
}
// FirstMRR.track("open", screen: "/home")
// FirstMRR.track("payment", extra: ["v": 9.99, "c": "USD", "i": transaction.id])Call track("open") on every open and return to the foreground, signup, activation, trial, then payment in the success callback of StoreKit, Google Play Billing, RevenueCat or Superwall.
Apple and Google review
- No IDFA, AAID,
identifierForVendoror device fingerprint: no App Tracking Transparency prompt. - An "Audience measurement" setting, on by default, that stops every send.
PrivacyInfo.xcprivacy: Product Interaction and Device ID, not linked to identity, no tracking, Analytics purpose; Purchase History if you send payments; UserDefaults with reason CA92.1.
The assisted installation in Distribution → Tracking writes this module for you and tells you what to declare in App Store Connect and Google Play.