Attribura iOS SDK
Capture the answer to your in-app “How did you hear about us?” question and tie it to the content you posted — including the word-of-mouth and App Store Search traffic no tracking link can see. Headless: you build the sheet, the SDK just tracks.
1 — Install
In Xcode: File → Add Package Dependencies… and paste https://github.com/martindochamp/attribura-ios. Or add it to your Package.swift:
.package(url: "https://github.com/martindochamp/attribura-ios", from: "0.1.0")2 — Get your token
Copy your ingest token from Settings → Integrations → Attribura SDK. It's the same per-org token the Superwall/Stripe webhooks use, and it's write-only— it can't read your data back.
3 — Configure once
import Attribura
// Once, as early as possible (App init / didFinishLaunching):
Attribura.configure(
token: "atb_ingest_xxx",
baseURL: URL(string: "https://api.attribura.com")!
)Signed in? Your real ingest token is already filled in above — just copy.
4 — Report the answer
// When the user taps an answer in YOUR sheet.
// Use the SAME user id your revenue provider reports (Superwall app_user_id),
// so the eventual purchase inherits the self-reported channel.
Attribura.reportSource(.instagram, userId: currentUserId)The SDK ships no UI. Here's a minimal SwiftUI sheet you own and wire up:
import SwiftUI
import Attribura
struct HeardAboutUsSheet: View {
let userId: String
var onDone: () -> Void
private let options: [(String, AttributionSource)] = [
("Instagram", .instagram), ("TikTok", .tiktok), ("YouTube", .youtube),
("A friend", .friend), ("A podcast", .podcast), ("App Store search", .appStoreSearch),
]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("How did you hear about us?").font(.title3.bold())
ForEach(options, id: \.0) { label, source in
Button(label) {
Attribura.reportSource(source, userId: userId,
prompt: "How did you hear about us?")
onDone()
}
.buttonStyle(.bordered)
}
}
.padding()
}
}Tip — randomize the option order
People rushing onboarding tend to tap the same position every time (usually the first one), which quietly biases your data toward whatever you list first. Shuffle the options per user so that noise averages out:
// Randomize the order once per user so "always tap the first option"
// habits don't skew your data — .shuffled() gives a fresh order each init.
private let options: [(String, AttributionSource)] = [
("Instagram", .instagram), ("TikTok", .tiktok), ("YouTube", .youtube),
("A friend", .friend), ("A podcast", .podcast), ("App Store search", .appStoreSearch),
].shuffled()Sources
Linkable channels — cross-checked against your recent posts:
Dark social — unlinkable, but the traffic every link misses:
How it resolves
| The answer | Method | Confidence |
|---|---|---|
| One recent post on that channel | self_report_verified | 0.60 — pins the exact post |
| Linkable channel, 0 or several recent posts | self_report | 0.45 — channel-level |
| Dark social (friend, podcast, App Store search…) | dark_social | 0.40 — no link could see it |
Deterministic signals (campaign token, discount code) always win — a survey answer never overrides a certified link. Attribution stays confidence-scored and directional, never deterministic.
Integrating with an AI assistant
Paste the SDK's llms.txtinto Claude or ChatGPT and ask it to wire the SDK into your app — it's a complete, self-contained integration guide.