state parameter. The provider echoes state back to your callback unchanged, so the referral key survives the round-trip to the identity provider and back — without cookies, tracking pixels, or anything ad-blockers can strip.
TL;DR — Pack the Partnero referral key into the OAuth
state value alongside a CSRF token. On the callback, decode state and call POST /v1/customers with partner.key (affiliate) or referring_customer.key (refer-a-friend). Works with every OAuth 2.0 / OIDC provider: Google, Facebook (Meta), GitHub, Apple, Microsoft (Azure AD), Auth0, Clerk, Supabase, and more.This guide assumes you already capture the referral key on the landing page (e.g. from
?aff=KEY). If not, start with Server-to-server (S2S) tracking → Step 1 or JavaScript tracking.Why social login breaks referral tracking
A standard referral flow attributes sign-ups using a URL parameter (?aff=KEY) or a first-party cookie. Social login disrupts both because the browser leaves your site for the identity provider before the user account is created.
- URL parameters are lost. After “Sign in with Google”, the browser ends up at
yoursite.com/auth/callback?code=…&state=…— a different URL from the landing page that carried?aff=KEY. - Cookies can be missing. Same-site first-party cookies usually survive a Google or Facebook redirect, but they commonly fail in three cases:
- In-app browsers (Instagram, TikTok, LinkedIn, Facebook) often partition or block storage.
- Apple Sign-In with
response_mode=form_postposts a cross-sitePOSTto your callback. Browsers do not sendSameSite=Laxcookies on cross-site POSTs. - Private / incognito sessions clear cookies between visits.
How the OAuth state parameter preserves the referral key
Thestate parameter is a built-in OAuth 2.0 / OIDC field that the provider returns to your callback unchanged. By packing the Partnero referral key into state (alongside a CSRF token), you guarantee it survives the redirect — independent of cookies, browser policy, or tracking blockers.
- Visitor lands on
yoursite.com?aff=REFERRAL_KEY. You store the key in their session (or read it again when they click the social-login button). - Visitor clicks “Sign in with Google” (or any provider). You generate a
statevalue containing a CSRF token and the referral key, then redirect to the provider. - Provider redirects back to your callback URL with the same
statevalue. - Your callback validates the CSRF token, extracts the referral key, creates the user, and calls
POST /v1/customerswithpartner.key(orreferring_customer.key).
Step 1: Build the OAuth state value
When the user clicks the social-login button, combine a CSRF token and the referral key into a single opaque string. JSON + base64url is the simplest format and is supported by every OAuth library.- Node.js
- PHP
- Python
Step 2: Handle the callback and create the customer in Partnero
When the provider redirects back, decodestate, verify the CSRF token, and use the recovered referral key in your POST /v1/customers call.
Affiliate programs
For affiliate programs, send the recovered referral key aspartner.key.
- Node.js
- PHP
- Python
Refer-a-friend programs
For refer-a-friend programs, send the recovered referral key asreferring_customer.key. Omit the field entirely if state didn’t carry a referral key.
- cURL
- Node.js
- PHP
- Python
Provider-specific guidance
The same pattern works for every OAuth 2.0 / OIDC identity provider — only the authorisation URL and library calls change. Below are notes for the most common providers used with Partnero.Google (Sign in with Google)
- Authorisation URL:
https://accounts.google.com/o/oauth2/v2/auth - Pass
state=<your-encoded-value>in the query string. Google returns it unchanged to yourredirect_uri. - Works with Passport.js (
passport-google-oauth20), NextAuth.js / Auth.js, Laravel Socialite, Authlib, Firebase Auth, and Supabase Auth.
Facebook / Meta
- Authorisation URL:
https://www.facebook.com/v18.0/dialog/oauth stateis round-tripped on the callback. Be aware that Facebook’s in-app browser (and Instagram’s) can drop cookies between landing and callback — thestateapproach avoids the problem entirely.
GitHub
- Authorisation URL:
https://github.com/login/oauth/authorize - GitHub strongly recommends
statefor CSRF protection and echoes it on the callback — perfect for piggybacking the Partnero referral key.
Apple (Sign in with Apple)
- Authorisation URL:
https://appleid.apple.com/auth/authorize - Apple supports
stateand, when you requestscope=name email, sends the callback asresponse_mode=form_post(a cross-sitePOSTto yourredirect_uri). Cookies withSameSite=Laxare not sent on cross-site POSTs, so thestateparameter is the only reliable carrier for the referral key with Apple sign-in.
Microsoft (Azure AD / Entra ID)
- Authorisation URL:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize - Full OIDC support;
stateis up to 2 KB and returned unchanged.
LinkedIn, Twitter/X, Discord, Slack, and others
- All standard OAuth 2.0 providers support
state. Use the same pattern: pack the Partnero referral key, redirect, decode on callback.
Auth library hints
- NextAuth.js (Auth.js) — add the referral key inside
authorization.params.state, or store it on theAccountrow during thesignInevent. - Passport.js —
passport.authenticate('google', { state }). - Laravel Socialite —
Socialite::driver('google')->with(['state' => $state])->redirect(); - Authlib (Python) — pass
statetoauthorize_redirect(). - Auth0 — use
appStateinloginWithRedirect, read it from theappStateargument inonRedirectCallback. - Clerk — pass
redirectUrlCompletewith the key as a query string, or useunsafeMetadataonsignUp. - Supabase Auth — include the key in
queryParamsofsignInWithOAuth, read it from the post-callback URL. - Firebase Authentication — use
signInWithRedirectand persist the referral key in your own session before the redirect; read it back whengetRedirectResult()resolves.
Fallback: first-party cookie
If you can’t (or don’t want to) modify the OAuthstate, set a first-party cookie on landing and read it from the callback request.
- On landing with
?aff=KEY, set aHttpOnlycookie:aff_key=KEY; Max-Age=2592000; Path=/; SameSite=Lax. - After the OAuth callback completes and the user is created, read the cookie from the request and call
POST /v1/customerswithpartner.key.
state:
- Safari ITP caps
document.cookie-set cookies at 7 days. (Server-set cookies are unaffected.) - In-app browsers may partition cookies away from your domain entirely.
- Apple Sign-In with
response_mode=form_post(cross-site POST callback) won’t sendSameSite=Laxcookies.
state; use cookies only when your OAuth library makes injecting state awkward.
Already using PartneroJS?
If you have PartneroJS installed, it setspartnero_partner (affiliate) or partnero_referral (refer-a-friend) as a first-party cookie on landing. In most flows this cookie survives the OAuth round-trip, so you have two options:
- Let PartneroJS handle it — call
po('customers', 'signup', ...)on the post-sign-in page. PartneroJS reads the cookie automatically and attributes the sign-up. - Read it server-side — extract the cookie from the callback request and pass it as
partner.keytoPOST /v1/customers.
state pattern above.
Track sales after sign-up
Once the customer exists in Partnero, track purchases like any other integration — callPOST /v1/transactions with the same customer key, or let a connected billing integration (Stripe, Paddle, Chargebee) do it automatically.
Frequently asked questions
Does this work with Sign in with Apple?
Yes — andstate is the only reliable way for Apple. When Apple’s response_mode=form_post is in play, the callback is a cross-site POST to your redirect_uri, which strips SameSite=Lax cookies. Encoding the Partnero referral key into state survives that POST.
Can I use the same approach across multiple OAuth providers?
Yes. Generate the samestate (CSRF + referral key) for every provider and decode it the same way in every callback. The code differs only in the authorisation URL.
What if I’m using NextAuth.js, Auth.js, or another framework?
Use the library’s built-instate / params hook to inject the referral key, then read it back inside the appropriate callback (signIn, redirect, jwt, or your custom callback handler) and call POST /v1/customers from there. See Auth library hints.
Do I still need PartneroJS if I’m using social login?
No. The OAuthstate pattern works without PartneroJS — it’s pure server-side. PartneroJS is still useful if you want client-side click tracking or auto-detect form sign-ups on non-OAuth flows. The two can coexist.
Can I attribute a sign-up that happens later (not in the OAuth callback)?
Yes. Persist the referral key in your database (e.g. on the user row) when you create the user from the OAuth callback, then send it to Partnero whenever the user’s account is provisioned — even if that happens days later. Partnero attributes based on the referral key you provide, not when the customer record is created.What if state is empty or invalid?
Reject the callback (return 400). An invalid state either means the user didn’t start the flow on your site, or the request was tampered with. Always validate the CSRF portion before trusting the referral key.
Checklist
- Capture
aff(or your program’s referral parameter) on the landing page. - Encode it into the OAuth
statevalue alongside a CSRF token before redirecting to the provider. - On the callback, validate CSRF, extract the referral key, and call
POST /v1/customerswithpartner.key(affiliate) orreferring_customer.key(refer-a-friend). - Track sales as usual via
POST /v1/transactions, or let a connected billing integration handle it.
Looking for the underlying API calls without an OAuth wrapper? See Server-to-server (S2S) tracking. For client-side tracking with cookies, see JavaScript tracking.
