Attribution
handleInbound, resolveReferral, and applyCode — how referral codes reach the SDK on every platform
Three methods cover every way a referral code arrives. handleInbound handles the already-installed path (a deep link opens your app). resolveReferral handles the fresh-install path (deferred attribution) and reports the outcome. applyCode is the manual-entry fallback that makes attribution complete.
The method names and outcomes are identical everywhere; the machinery underneath is platform-specific — URL params and a first-party cookie on web, the clipboard handoff on iOS, the Play Install Referrer on Android. No fingerprinting on any platform. The full model lives in Attribution.
handleInbound
Extracts a referral code from an incoming URL and applies it. Returns immediately; validation happens in the background. Non-referral URLs are ignored, so it is safe to forward every incoming URL.
| Parameter | Type | Description |
|---|---|---|
url | URL | The incoming Universal Link / App Link / web URL. |
Returns true if the URL contained a referral code the SDK will apply.
invitebase.handleInbound(url?: string): InboundReferral | nullOn web this returns the parsed referral (or null) instead of a boolean, and url defaults to window.location.href. With autoResolve on, configure already handles the landing URL — call this yourself for client-side routing or custom entry points.
const inbound = invitebase.handleInbound();
if (inbound) {
showWelcomeBanner(`You were invited by ${inbound.referrerName ?? 'a friend'}`);
}@discardableResult
Invitebase.handleInbound(_ url: URL) -> BoolHand over URLs from onOpenURL, NSUserActivity, or a custom scheme:
WindowGroup {
ContentView()
.onOpenURL { url in
Invitebase.handleInbound(url)
}
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
if let url = activity.webpageURL {
Invitebase.handleInbound(url)
}
}
}Requires the Associated Domains entitlement — one-time setup in iOS setup.
fun Invitebase.handleInbound(uri: Uri?): BooleanNull-safe — passing null is a no-op returning false. Hand over the launch intent's data URI in both entry points:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Invitebase.handleInbound(intent?.data)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Invitebase.handleInbound(intent.data)
}
}Requires a verified App Links intent filter — one-time setup in Android setup.
handleInbound(url: string): booleanThe provider listens for incoming URLs automatically on both Expo and bare RN. If you manage URL subscriptions yourself, forward them explicitly:
const invitebase = useInvitebase();
Linking.addEventListener('url', ({ url }) => {
invitebase.handleInbound(url);
});Full wiring in React Native setup.
static bool Invitebase.handleInbound(Uri uri)Works with any deep-link package (app_links, go_router, uni_links):
appLinks.uriLinkStream.listen((uri) {
Invitebase.handleInbound(uri);
});Cold-start and router-based wiring in Flutter setup.
resolveReferral
Runs (or reports) deferred attribution for a fresh install and returns one of three outcomes:
- attributed — a code was recovered and applied; show your referee welcome moment.
- needsManualEntry — automatic attribution missed. The
signaltells you how confident to be:strongmeans a referral is likely (prompt prominently),softmeans probably organic (offer a skippable field). - organic — no referral signal at all.
No parameters.
invitebase.resolveReferral(): Promise<ReferralResolution>type ReferralResolution =
| { status: 'attributed'; referral: InboundReferral }
| { status: 'organic' };On web there is no deferred-install gap, so this resolves from the URL/cookie state immediately — and needsManualEntry never occurs.
const resolution = await invitebase.resolveReferral();
if (resolution.status === 'attributed') {
console.log('Referred with code', resolution.referral.code);
}Invitebase.resolveReferral() async -> ReferralResolutionpublic enum ReferralResolution {
case attributed(InboundReferral) // code recovered and applied
case needsManualEntry(signal: ManualEntrySignal) // clipboard missed or was declined
case organic // no referral signal at all
}
public enum ManualEntrySignal {
case strong // a URL was present but unreadable — likely a referral
case soft // no URL was ever present — probably organic
}switch await Invitebase.resolveReferral() {
case .attributed(let referral):
showWelcome(from: referral.referrerName, offer: referral.refereeOffer)
case .needsManualEntry(let signal):
presentCodeEntry(prominence: signal == .strong ? .strong : .soft)
case .organic:
break
}The mechanism is the clipboard handoff — deterministic, no permission modal when integrated as recommended. How it works, step by step: iOS setup.
suspend fun Invitebase.resolveReferral(): ReferralResolutionsealed class ReferralResolution {
data class Attributed(val referral: InboundReferral) : ReferralResolution()
data class NeedsManualEntry(val signal: ManualEntrySignal) : ReferralResolution()
object Organic : ReferralResolution()
}
enum class ManualEntrySignal { STRONG, SOFT }lifecycleScope.launch {
when (val resolution = Invitebase.resolveReferral()) {
is ReferralResolution.Attributed ->
showWelcome(resolution.referral.referrerName, resolution.referral.refereeOffer)
is ReferralResolution.NeedsManualEntry ->
showCodeEntry(prominence = resolution.signal)
ReferralResolution.Organic -> Unit
}
}The mechanism is the Play Install Referrer — deterministic and near-100% for Play Store installs; sideloads resolve to NeedsManualEntry(SOFT). Details: Android setup.
resolveReferral(): Promise<ReferralResolution>Deferred attribution runs natively per platform with no JavaScript involvement. In components, prefer the reactive useReferralStatus hook:
const status = useReferralStatus();
if (status.resolution === 'attributed' && status.referral) {
return <WelcomeCard title={`Invited by ${status.referral.referrerName ?? 'a friend'}`} />;
}
if (status.resolution === 'needsManualEntry') {
return <CodeEntrySheet prominence={status.signal ?? 'soft'} />;
}static Future<ReferralResolution> Invitebase.resolveReferral()sealed class ReferralResolution {}
class Attributed extends ReferralResolution {
final InboundReferral referral;
}
class NeedsManualEntry extends ReferralResolution {
final ManualEntrySignal signal; // ManualEntrySignal.strong | .soft
}
class Organic extends ReferralResolution {}final resolution = await Invitebase.resolveReferral();
switch (resolution) {
case Attributed(:final referral):
showWelcome(referral.referrerName, referral.refereeOffer);
case NeedsManualEntry(:final signal):
showCodeEntry(prominent: signal == ManualEntrySignal.strong);
case Organic():
break;
}The mechanism is native per platform — clipboard handoff on iOS, Play Install Referrer on Android — through the platform channel with no Dart-side work.
Resolve early — deferred-attribution signals are volatile between install and first launch — but surface any manual-entry UI at a contextual onboarding moment. Every branch emits telemetry, so your attribution funnel shows the measured rates.
applyCode
Applies a manually entered referral code — the fallback when automatic attribution misses. Validates against the API; invalid or expired codes throw an invalid_referral_code error, so your UI can keep the user in the entry flow instead of a dead end.
| Parameter | Type | Description |
|---|---|---|
code | string | The referral code, e.g. a1b2c3. Case-insensitive, whitespace-trimmed. |
Returns an InboundReferral — { code, campaignId, referrerName, refereeOffer }, with referrerName privacy-safe and refereeOffer display-ready (e.g. "1 month free").
invitebase.applyCode(code: string): Promise<InboundReferral>try {
const referral = await invitebase.applyCode(inputValue);
showConfirmation(`Invited by ${referral.referrerName} — ${referral.refereeOffer}`);
} catch (err) {
if (err instanceof InvitebaseError && err.code === 'invalid_referral_code') {
showFieldError('That code doesn’t look right — check it and try again.');
}
}Invitebase.applyCode(_ code: String) async throws -> InboundReferraldo {
let referral = try await Invitebase.applyCode(enteredCode)
showConfirmation("Invited by \(referral.referrerName ?? "a friend") — \(referral.refereeOffer ?? "")")
} catch InvitebaseError.invalidReferralCode {
showFieldError("That code doesn’t look right — check it and try again.")
}The SDK ships a themeable InvitebaseCodeEntryView (and InvitebasePasteButton) over this method — see iOS setup.
suspend fun Invitebase.applyCode(code: String): InboundReferrallifecycleScope.launch {
try {
val referral = Invitebase.applyCode(enteredCode)
showConfirmation("Invited by ${referral.referrerName ?: "a friend"}")
} catch (e: InvitebaseException.InvalidReferralCode) {
showFieldError("That code doesn’t look right — check it and try again.")
}
}The SDK ships a themeable InvitebaseCodeEntry composable over this method, with validation states and strong/soft prominence variants.
applyCode(code: string): Promise<InboundReferral>const invitebase = useInvitebase();
try {
const referral = await invitebase.applyCode(input);
showConfirmation(`Invited by ${referral.referrerName ?? 'a friend'}`);
} catch (err) {
setFieldError('That code doesn’t look right — check it and try again.');
}The package exports a CodeEntrySheet component over this method — see React Native setup.
static Future<InboundReferral> Invitebase.applyCode(String code)try {
final referral = await Invitebase.applyCode(controller.text);
showConfirmation('Invited by ${referral.referrerName ?? 'a friend'}');
} on InvalidReferralCodeException {
setState(() => fieldError = 'That code doesn’t look right — try again.');
}