Affiliate
Pay commissions on sales from your website, your mobile app, or both. One app, one referral link.
Website, mobile app, or both
An Affiliate app is defined by what you connect to it, not by what you picked at creation.
There used to be two separate app types. There is now one. You connect a web payment provider (and give us your site URL), a mobile purchase provider (and give us your App Store / Play Store URLs), or both. Everything else — affiliates, commission rates, products, the referral link, the payout schedule — is shared.
Web rail
Stripe, Polar, Paddle, Whop, Shopify, or WooCommerce, plus a website URL. Turns on the ?ref= link, the web SDK, and web checkout attribution.
Mobile rail
RevenueCat, Adapty, Apple in-app purchases, Google Play, or Stripe, plus your store URLs. Turns on store routing, the mobile SDKs, and install matching.
You can add the second rail later. Editing a live app to connect a mobile provider (or a website and a web provider) keeps the same app, the same affiliates, and the same referral links. Nothing has to be recreated.
How it works
End-to-end flow from app creation to payout.
Selling on your website
Business owner creates a web affiliate app on Affiliateo.
Sets commission rate (percentage or flat) and connects their Stripe account.
Adds the Affiliateo tracking SDK to their website.
Affiliates join the app and get unique referral links.
When someone clicks a referral link and makes a purchase, the affiliate earns commission.
On the 1st of the next month, commissions become payable.
Business owner clicks "Pay" to transfer commissions to affiliate wallets.
Selling inside your mobile app
Business owner creates a mobile affiliate app on Affiliateo.
Sets commission rate and connects RevenueCat.
Adds the Affiliateo SDK to their mobile app.
Affiliates join the app and get unique referral links.
When someone installs through the link and makes an in-app purchase, the affiliate earns commission.
On the 1st of the next month, commissions become payable.
Business owner clicks "Pay" to transfer commissions to affiliate wallets.
Running both? Do both. They are the same app, so an affiliate who drives a website sale and an app install is one affiliate with one balance.
One link, routed by device
Affiliates get one link. Where it lands is decided when it is clicked.
An iPhone or iPad click goes to your App Store URL, when you have set one.
An Android click goes to your Play Store URL, with the Play install referrer appended.
Everything else (desktop, tablets we can't place, anything unusual) goes to your website, with ?ref= appended.
Mobile-only app with no web rail? The click falls through to whichever store URL exists, so a desktop visitor still lands on a store page they can send to their phone.
Referral links come in three formats: a short link (affiliateo.com/r/CODE), a username link, and a direct link on your own domain (yoursite.com?ref=username). Direct links skip the redirect entirely; the tracking SDK captures the ?ref= parameter when the visitor lands.
How referrals are tracked
The two rails do not track the same way. Plan your program around the difference.
Website | Mobile app | |
|---|---|---|
| Method | Deterministic | Probabilistic |
| What carries the referral | The ?ref= parameter on the link. The SDK reads it on landing and writes it into first-party storage on your own domain. | Nothing. The App Store strips every parameter off a link before the install, so the code the affiliate shared does not reach the installed app. |
| How the sale is matched | The exact code that was written down is read back at checkout and passed to your payment provider. The affiliate who was clicked is the affiliate who gets paid. | On first app open the SDK reports the device to us and we look for a recent click that plausibly came from it: IP address (exact, or the same /24 subnet), operating system, country, and how recent the click was. The best-scoring click wins. |
| Matching window | No separate step. The code is read at checkout. | 7 days from the click. Older clicks are not considered, and recent clicks score higher than old ones. |
| When it can be wrong | Essentially never. It fails only if the visitor clears storage or switches browsers before buying. | Shared office or carrier IPs, VPNs, and long gaps between click and install can miss a match. A subnet-only match needs corroborating signals before it counts at all. |
| Attribution window on the sale | 60 days, rolling. Reset by every tagged pageview and every conversion. | 60 days, rolling, counted from the install match. |
Install attribution is a best-effort match, not a receipt. Expect a share of installs to go unattributed, and expect your mobile numbers to be a floor rather than an exact count. This is a property of the App Store, not of Affiliateo: no one can pass a parameter through an Apple install, so every attribution vendor is making the same kind of guess.
Android is a partial exception: the Play Store does forward an install referrer, and our redirect appends one. Today Affiliateo reads it to work out which ad network drove the install. Affiliate credit on both stores still comes from the fingerprint match above.
Once a purchase is attributed, both rails behave identically: the rate is locked at conversion time and every renewal of that subscription keeps crediting the same affiliate, however old the original click is.
Commission structure
Flexible commission models for your business.
Type | Example | Description |
|---|---|---|
| Percentage | 20% of each sale | Affiliate earns a percentage of the transaction amount |
| Flat rate | $5 per sale | Affiliate earns a fixed amount per conversion |
| Per-product custom | $5 on one product, 15% on another | Set one default rate for the app, then override individual products with their own percentage or flat amount. Products you don't customize fall back to the default rate. |
A 5% platform fee applies to each affiliate-referred sale, in addition to the commission you set.
Commission rate is locked in when an affiliate joins. The rate cannot be lowered for existing affiliates.
Commission is calculated on the gross sale price, not the net amount after store fees.
Maximum commission, per rail
The cap depends on who takes a cut before the money reaches you.
Sale goes through | Max commission | Why |
|---|---|---|
| Apple in-app purchases, Google Play, RevenueCat, Adapty | 70% | Apple and Google take up to 30% off the top. A higher rate would lose money on every sale. |
| Stripe, Polar, Paddle, Whop, Shopify, WooCommerce | 90% | No store cut. The remaining 10% covers payment processing and the platform fee. |
| Flat payouts (any rail) | $50 | Applies to the app-wide flat rate. Per-product flat payouts are set per product and are not capped at $50. |
The minimum percentage rate is 1%. Note that a mobile app billed through Stripe rather than the stores sits in the 90% row: nobody takes a store cut, so the store ceiling does not apply.
Where the cap is applied
Per-product rates are capped when you save them. Each product in your rates editor comes from exactly one catalogue, so we know its rail at that moment: a product from your mobile catalogue caps at 70%, a product from your web catalogue caps at 90%, on the same app.
The app default rate and per-affiliate deals are capped at the sale. One number covers both rails, so it cannot be judged at config time. You may save 90%; a store-mediated sale is then paid at 70% and everything else at 90%. We never reject the save, we just never pay more than the rail allows.
Apple and Google take up to 30% of each sale. The maximum affiliate commission is 70% to account for this.
Payment providers
Affiliateo connects to six different payment platforms. Each provider has its own setup and install instructions, but all of them feed into the same affiliate tracking + commission logic.
Stripe
OAuth ConnectBest for SaaS and digital products. Click Connect → approve in Stripe popup → done. Our SDK or paste-in script captures the affiliate, you pass it to Stripe metadata at checkout.
Storefront install
npm install @affiliateo/web (or paste t.js)
Polar
OAuth ConnectOpen-source Stripe alternative for digital creators. Same OAuth shape as Stripe. single click to connect.
Storefront install
npm install @affiliateo/web (or paste t.js)
Paddle
Paste API keyMerchant-of-Record platform for SaaS. Paste a Paddle API key from your dashboard; we auto-create the webhook subscription on your account.
Storefront install
npm install @affiliateo/web (or paste t.js)
Whop
Paste API keyCreator monetization platform (memberships, communities, courses). Paste a Whop Company API key; we auto-create the webhook.
Storefront install
npm install @affiliateo/web (or paste t.js)
Shopify
Paste store credentialsFor Shopify dropshippers. Create a custom app in your Shopify admin, paste shop domain + access token + API secret. We auto-create the webhooks.
Storefront install
Paste shopify-affiliate.js script into theme.liquid
WooCommerce
OAuth ConnectFor WordPress-based stores. Type your shop URL → click Connect → approve in your WC admin popup. WooCommerce generates and sends us the keys directly.
Storefront install
Download the Affiliateo WordPress plugin and activate
Pick how you take payments
Four ways to wire purchases into your affiliate pipeline. Pick one (or combine Apple + Google for direct native).
RevenueCat
One integration covers both stores. Easiest setup, third-party in the loop.
Adapty
RevenueCat alternative covering the App Store and Play Store. No API key needed: paste our webhook URL and secret into Adapty (Integrations → Webhook) and the app connects automatically.
Apple App Store (native) →
App Store Server Notifications V2 + Server API directly. iOS only. No third-party.
Google Play (native) →
Real-Time Developer Notifications + Play Developer API directly. Android only. No third-party.
Stripe
For non-IAP purchases (physical goods, services). Apple + Google require IAP for digital goods.
One mobile provider per app. Connecting one alongside an existing web provider is what makes the app dual-rail. it does not replace the web side.
SDK integration
Install the SDK for each rail you run. A dual app installs both, using the same app ID on either side.
Website
Add the Affiliateo SDK to your website to track affiliate referrals and attribute purchases.
Applies to Stripe, Polar, Paddle, and Whop. Shopify and WooCommerce use their own install paths. see below.
1. Install
npm install @affiliateo/web
2. Add the provider to your app
import { AffiliateoProvider } from '@affiliateo/web';
export default function RootLayout({ children }) {
return (
<AffiliateoProvider appId="YOUR_APP_ID">
{children}
</AffiliateoProvider>
);
}3. Pass attribution to Stripe checkout
'use client'
import { getAttribution } from '@affiliateo/web';
async function handleCheckout() {
const attribution = getAttribution();
const res = await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ attribution }),
});
const { url } = await res.json();
window.location.href = url;
}export async function POST(req) {
const { attribution } = await req.json();
const session = await stripe.checkout.sessions.create({
line_items: [...],
mode: 'payment',
metadata: attribution,
});
return Response.json({ url: session.url });
}That's it! The provider tracks sessions and captures affiliate attribution automatically. Stripe metadata links each purchase back to the referring affiliate.
Mobile app
Add the Affiliateo SDK to your mobile app. Choose your platform below.
1. Install
npm install @affiliateo/react-native @react-native-async-storage/async-storage @react-native-community/netinfo
2. Wrap your app with the provider
import { AffiliateoProvider } from '@affiliateo/react-native';
export default function App() {
return (
<AffiliateoProvider appId="YOUR_APP_ID">
<YourApp />
</AffiliateoProvider>
);
}Track a screen (one line)
import { useAffiliateoScreen } from '@affiliateo/react-native';
export default function HomeScreen() {
useAffiliateoScreen('HomeScreen');
return <YourScreenUI />;
}Fingerprint matching, RevenueCat attribution, and session tracking are automatic.
Track conversion goals
Goal names must be lowercase letters, digits, underscores, hyphens, or colons on both rails, and optional metadata (a second argument) is bounded to 4KB if you want to slice funnels by property later.
Website
Pageviews auto-track. For specific moments that matter to your funnel (signup_completed, trial_started, initiate_checkout, etc.), you have two options depending on your integration: npm SDK users call track() from a React hook (and trackPageview() for modal or virtual-route pageviews). Script-tag users can additionally drop a data-affiliateo-goal attribute on any button, or data-affiliateo-scroll on a section to fire when it scrolls into view (great for landing pages where you want to know which sections convert).
Fire a custom goal from React
'use client'
import { useEffect } from 'react'
import { useAffiliateRef } from '@affiliateo/web'
export default function SignupSuccess() {
const { track } = useAffiliateRef()
useEffect(() => {
track('signup_completed', { plan: 'free' })
}, [])
return <div>Welcome!</div>
}Track virtual pageviews (modals, SPA routes)
'use client'
import { useEffect } from 'react'
import { useAffiliateRef } from '@affiliateo/web'
export default function PricingModal({ open }: { open: boolean }) {
const { trackPageview } = useAffiliateRef()
useEffect(() => {
// Fire a pageview for the modal so it shows up in your funnel
// even though no real route change happened.
if (open) trackPageview('/pricing-modal')
}, [open])
return null
}Mobile app
On native SDKs (React Native, Swift, Kotlin, Flutter) screens are tracked when you call page(name) per screen. This is the Mixpanel / Amplitude model. predictable, no ghost events. For specific moments that matter to your funnel (signup_completed, trial_started, feature_used, etc.), call track(). Vanilla / WebView (Capacitor, Ionic, Cordova) loads the m.js script and pageviews on URL change are auto-fired, same as the web SDK.
Track a custom event
import { useAffiliateRef } from '@affiliateo/react-native';
function SignupButton({ onNext }) {
const { track } = useAffiliateRef();
return (
<Pressable onPress={() => {
track('signup_completed');
onNext();
}}>
<Text>Continue</Text>
</Pressable>
);
}Identify signed-in users
Without identify(), the same person browsing on phone and buying on laptop counts as two unrelated visitors and your funnel splits across devices. Call it once after sign-in so Affiliateo can stitch the journey to one person. Idempotent. safe to fire on every page load or app launch. On a dual app this is what ties a website visit and an app install to the same human.
Stitch this browser to a logged-in user
'use client'
import { useEffect } from 'react'
import { useAffiliateRef } from '@affiliateo/web'
import { useUser } from './your-auth'
export default function AuthBoundary({ children }) {
const { identify } = useAffiliateRef()
const user = useUser()
useEffect(() => {
if (user) identify(user.id)
}, [user])
return <>{children}</>
}Landing page direct links
Give affiliates a clean link on your own domain
Add a landing page URL in your app settings and affiliates get a fourth link format: yoursite.com?ref=username, with no redirect through affiliateo.com. Put one of these snippets on that page and every arrival with a ref code is recorded, so installs still match back to the right affiliate. Pick one flavor:
Add to your landing page
<script defer data-app="YOUR_APP_ID" data-redirect="store" data-ios-url="https://apps.apple.com/us/app/your-app/id123456789" data-android-url="https://play.google.com/store/apps/details?id=com.yourapp" src="https://affiliateo.com/t.js" ></script>
The page instantly forwards affiliate visitors (?ref= in the URL) to the right store for their device; everyone else sees your page normally. Clean link, less friction.
Fastest: redirect at your server
Bounce affiliate visitors before your page even loads
The snippet above runs in the browser, so visitors briefly see your page before the bounce. If you control your server or edge, you can skip that entirely: 302-redirect any request arriving with ?ref= to https://affiliateo.com/r/your-app-slug/{ref} (your app's slug is in its Affiliateo URL). The visitor's browser follows the redirect, so the click is logged with their real IP and device and installs match exactly like short links. Important: do not report the click server-to-server instead, because it would arrive from your server's IP and install matching would break. With this in place you don't need the landing page snippet at all.
Add to your server
export default {
async fetch(request, env) {
const url = new URL(request.url)
const ref = url.searchParams.get('ref')
if (ref && request.method === 'GET') {
return Response.redirect(
`https://affiliateo.com/r/your-app-slug/${encodeURIComponent(ref)}`,
302,
)
}
return env.ASSETS.fetch(request) // or your existing handler
},
}Any host works the same way: if the request has ?ref=, send a 302 to your app's affiliateo.com/r/ link. Organic traffic falls through to your normal page.
Multi-domain attribution
If your property spans more than one domain (marketing site on marketing.com and your app on app.merchant.com), affiliate ref codes die at every domain boundary because browsers sandbox storage by origin. Without help, an affiliate who sends traffic to your marketing site gets no commission when the visitor signs up on your app domain. Add the other domains via data-allowed-hostnames (script tag) or allowedHostnames (npm SDK prop) and the SDK auto-decorates outbound links with the visitor identity. The receiving domain reads those params on landing, strips them from the URL bar, and the funnel sees one continuous visitor. Both sides need the SDK installed and both need to list the OTHER domains (each side names everyone except itself).
Pass allowedHostnames to the provider
import { AffiliateoProvider } from '@affiliateo/web';
export default function RootLayout({ children }) {
return (
<AffiliateoProvider
appId="YOUR_APP_ID"
allowedHostnames={['app.merchant.com', 'docs.merchant.com']}
>
{children}
</AffiliateoProvider>
);
}Development & debugging
By default the SDK is configured for production safety: localhost is blocked (so npm run dev doesn't pollute your funnel), iframes are blocked (so scrapers and preview tools don't count as pageviews), and debug logs are silent. Turn any of these on with the props or attributes below. Also exposed: a flush() method to force queued events out the door right before a redirect, so they don't get lost when the page navigates away.
Opt-in dev flags (allowLocalhost, allowIframe, debug)
import { AffiliateoProvider } from '@affiliateo/web';
export default function RootLayout({ children }) {
return (
<AffiliateoProvider
appId="YOUR_APP_ID"
// Turn on for end-to-end tests against localhost.
// Leave false (the default) in production.
allowLocalhost={process.env.NODE_ENV !== 'production'}
// Print decision logs to the browser console. Useful when
// investigating "why aren't my events showing up?".
debug={process.env.NODE_ENV !== 'production'}
// Set to true if your site is legitimately embedded inside
// an iframe (e.g. an embed widget shipped to partners).
// allowIframe
>
{children}
</AffiliateoProvider>
);
}Force flush before a redirect
'use client'
import { useAffiliateRef } from '@affiliateo/web'
export default function CheckoutButton() {
const { track, flush } = useAffiliateRef()
return (
<button onClick={async () => {
track('initiate_checkout', { plan: 'pro' })
// Force events out NOW before the redirect kills the page.
// Default flush timer is every 5s, too slow for a redirect.
await flush()
window.location.href = '/api/checkout'
}}>
Subscribe
</button>
)
}allowLocalhost matches localhost, 127.0.0.1, RFC1918 IPs, and the reserved .local / .test / .example / .invalid / .localhost / .lvh.me / .localtest.me TLDs. Production-style hosts (.vercel.app, .pages.dev, custom domains) are always allowed.
Shopify storefront install
Drop a single script into your theme. It captures affiliate ref-codes from URL parameters, syncs them into Shopify cart attributes, and tracks visitor pageviews. all in one tag.
Paste before </body> in layout/theme.liquid
<script defer data-app="YOUR_APP_ID" src="https://affiliateo.com/shopify-affiliate.js" ></script>
In Shopify admin → Online Store → Themes → Edit code → open layout/theme.liquid → paste before </body> → Save. Works on every Online Store 2.0 theme (Dawn, Crave, Sense, etc.) and on headless / Hydrogen storefronts.
WooCommerce plugin install
Download the Affiliateo WordPress plugin, install it on your store, and paste your app ID. The plugin handles ref-code capture (cookie + checkout hook → order meta_data) and visitor analytics.
Download affiliateo-woocommerce.zip from your wizard's install step.
In your WordPress admin: Plugins → Add New → Upload Plugin → choose the .zip → Install Now → Activate.
Go to WooCommerce → Settings → Advanced → Affiliateo and paste your app ID. Save.
Affiliate links work immediately. The plugin captures the ?ref= parameter, persists it through checkout, and writes it to the order's _affiliateo_ref meta. which our webhook handler picks up automatically.
RevenueCat setup
Connect RevenueCat to track in-app purchases.
Go to your RevenueCat dashboard → Project Settings → API Keys.
Create a new V2 secret key.
Paste the key in the app settings on Affiliateo.
Affiliateo automatically creates a webhook in your RevenueCat project.
Once connected, purchases are tracked automatically and attributed to the referring affiliate via subscriber attributes.
Store URLs
Direct users to the correct app store.
Embedded affiliate signup
Add a "Become an affiliate" button to your site and enroll your own users through the public API, without them ever leaving it.
Generate a read & write API key in your dashboard's API tab and keep it on your server. Never expose it in client-side code.
When a user clicks your button, send their email from your backend to POST /api/v1/businesses/{slug}/apps/{appId}/affiliates. The Affiliateo account is created automatically if it doesn't exist, and the response includes their short link, username link, and lifetime stats.
Show the links in your UI and render a QR code from the short link. Call the same endpoint again any time to refresh clicks, sales, and earnings. It never creates duplicates.
Data & privacy
Exactly what we read from your payment provider and your app, and what we don't.
Website sales
When a sale happens on your connected Stripe account, Stripe sends us a webhook event. We don't make API calls against your account or pull data on our own, we only read what's needed to attribute the sale and pay the affiliate.
What we read from each sale
What we never read or store
Stripe Connect doesn't offer a read-only scope for payment platforms, every Connect platform uses the same access level. We don't poll your account or run reports; we only listen for webhook events from sales that flowed through an affiliate-tagged checkout session.
Mobile app sales
When an in-app purchase happens in your app, RevenueCat sends us a webhook event. We don't poll your RevenueCat account or pull data on our own, we only read what's needed to attribute the sale and pay the affiliate.
What your RevenueCat API key is used for
We never call subscriber, customer, entitlement, offering, or receipt endpoints. The key is only used to install and verify our own webhook.
What we read from each purchase event
What we never read or store
RevenueCat doesn't currently offer scoped API keys for partners. We work around this by only making the four specific calls listed above and ignoring every other field RevenueCat sends in webhook payloads.
Payout schedule
When and how affiliates get paid.
Commissions are tracked in real time as purchases happen, on either rail. On the 1st of the next month, commissions become payable.
Affiliate drives a sale, commission is recorded immediately.
On the 1st, pending commissions become payable.
Business owner reviews and clicks "Pay" to transfer to affiliate wallets.
Mobile affiliate payouts are made from the business owner's wallet balance. Ensure sufficient funds are available.
Stripe payment processing fees apply separately. See the Fees page for full pricing details.