Docs · Integration
Integrate conversion tracking on your SaaS
Pay your Raws.dev ambassadors a fixed amount each time one of their referrals signs up on your SaaS. This page covers the integration end-to-end — copy-pasteable snippets, what to fire, security and privacy guidance.
1. What it is
Raws.dev is the platform that hosts your project's ambassador program — directory, missions, leaderboards, group chat, payouts. Per-signup commissions extend this to events that happen on your own SaaS:
- An ambassador shares their personal referral link
raws.dev/r/<project>/<handle>. - The visitor lands on your SaaS via the redirect with
utm_source=raws&utm_campaign=<handle>appended. - The visitor signs up. You fire
raws('event', 'conversion', { external_id }). - Raws.dev debits your prepaid wallet and pays the ambassador via Stripe Connect.
2. Install the tag
One <script> in <head> on every page of your SaaS:
<script async src="https://raws.dev/raws.js?id=YOUR_PROJECT_TOKEN"></script>
Find your token in the project's ambassador hub → "Pay per signup on your SaaS" → JS tag tab. The token is per-project and regeneratable; treat it as low-secret (it's exposed in your HTML).
- Reads
utm_sourceandutm_campaignfrom the URL. - If
utm_source=raws, stores the campaign value (the ambassador handle) inlocalStorageunderraws_ambassadorwith a 30-day expiry. - Exposes
window.raws(action, name, payload).
Zero network activity until you call raws('event', ...).
3. Fire conversions
On your signup thank-you page (or whichever event qualifies as a paid conversion):
raws('event', 'conversion', { external_id: '42' });
When should I fire?
Fire at the moment that justifies the per-signup amount you configured. Common patterns:
- Standard SaaS: after a successful account creation (and email verification, if you have one).
- Freemium: at activation (1st meaningful action) or at upgrade to paid — not at the bare signup, otherwise you pay for empty accounts.
- Crowdfunding: on confirmed pledge, not the intent.
- Marketplace: first listing published or first purchase.
One project = one configured payout. Fire the same event consistently across your funnel; external_id dedupe ensures the same user never pays twice even if you accidentally fire on every page reload.
What is external_id?
Your internal identifier for the thing being counted (usually the new user). Must be:
- Stable — never changes for the same conversion.
- Unique — different users get different ids.
- Non-PII — we store this for the dispute window; prefer ids that aren't email/username.
Recommended
user.id(numeric primary key)UUID v4generated server-side- Stripe customer id
Avoid
- Email address (PII, mutable)
- Username (mutable on most platforms)
- Random ids generated client-side per page
Server-side fallback (no JS)
If you'd rather fire from your backend (more reliable for adblock-heavy audiences, but requires you to plumb the ambassador handle through to your conversion event):
curl "https://raws.dev/c/YOUR_PROJECT_TOKEN?ref=AMBASSADOR_HANDLE&external_id=USER_ID"
200 OK + a 1×1 transparent GIF, or JSON when Accept: application/json. Idempotent on (project, external_id).
4. Test & activate
Every project starts in test mode. While in test mode, conversions are recorded for visibility but no money flows: your wallet isn't debited, nothing is transferred to ambassadors. Use this to verify the install end-to-end before going live.
- Embed the JS tag and your conversion event call.
- Visit your own SaaS via
https://raws.dev/r/<project-slug>/<your-handle>, complete the signup flow. - Open the project's ambassador hub. The "Recent fires" feed polls every 10s — your event appears with status
test. - Click Activate payouts. From now on, real conversions debit your wallet and pay ambassadors.
- Need to debug later? Hit Pause — incoming events keep landing as
testuntil you re-activate.
You can also force test mode on a single fire by passing ?test=1 on the pixel URL — useful for staging environments that share the production token.
5. CSP & security
If your site uses Content Security Policy (helmet, nginx, framework middleware), allowlist Raws.dev:
Content-Security-Policy:
script-src 'self' https://raws.dev;
connect-src 'self' https://raws.dev;
img-src 'self' https://raws.dev;
script-src for the tag itself, connect-src for the fetch() call to /c/<token>, img-src for the pixel fallback when fetch isn't available.
external_id blunts most abuse — but a fresh token cleanly invalidates leaked embeds.
6. Privacy & GDPR
Data we store per fire
project_id,ambassador_user_id,amount_cents, status, timestamp.external_id(your identifier — keep it non-PII).source_ip(IPv4/IPv6, max 45 chars),user_agent(max 500 chars) — used solely for fraud review during the dispute window.
Browser storage
The tag uses localStorage (key raws_ambassador, 30-day TTL) — not cookies. Only the ambassador handle is stored. No third-party cookies are ever set.
Consent & categorisation
localStorage isn't a "cookie" under ePrivacy strictly speaking, but EU data protection authorities increasingly treat persistent client-side storage similarly. Safe play: load raws.js only after the visitor accepts Marketing or Analytics cookies in your consent banner.
Privacy policy blurb
Drop-in paragraph for your privacy policy:
Conversion attribution — Raws.dev
We use Raws.dev (operated by [your legal entity], based in [country]) to attribute signups to ambassadors who referred them, so we can pay those ambassadors a commission. When you arrive on our site via an ambassador link, your browser stores a non-personal identifier in localStorage for up to 30 days. When you complete a qualifying signup, we send Raws.dev your account id, IP address and user-agent string, used solely for ambassador attribution and fraud review. No third-party cookies are set. You can clear this storage at any time from your browser. See https://raws.dev/legal for Raws.dev's own data handling.
A Data Processing Agreement is available on request — email info@raws.dev with your project handle and we'll send the DPA template.
7. Troubleshooting
- My fire isn't appearing in "Recent fires".
- Open browser devtools, Network tab. Trigger the conversion. Look for a request to
/c/<token>. If it doesn't fire: the tag didn't load (check CSP, check the script URL, check the project is still active). If it does fire but no row appears: the request is probably blocked — checkconnect-srcin your CSP. - Status is
testeven after I clicked Activate. - The fire was sent with
?test=1(check your URL, your env config, or any staging-specific pixel). Or the project flipped back to test mode (someone clicked Pause). - Status is
unfunded. - Your wallet balance was below the per-signup amount when the fire landed. Top up — queued unfunded rows release FIFO immediately on top-up success.
- Same user fires twice but only one row exists.
- Working as designed.
(project_id, external_id)is unique — duplicate fires are silently absorbed. - My ambassador hasn't received the money.
- The ambassador needs a Stripe Connect account to receive payouts. If they haven't onboarded, the row stays
pendingand settles automatically when they finish (no action needed on your side).
8. FAQ
- Can I have different payouts for different ambassadors?
- Not at this layer. The per-signup amount is per-project. If you need tiered payouts, run distinct projects (e.g. one for affiliates, one for power users) and send referrals to the right one.
- Can I revert a payout I disagree with?
- Yes — within 7 days of the fire, you can dispute a conversion from the project's hub. The Stripe transfer is reversed (clawed back to your wallet) and the row is marked
reversed. - Does Raws.dev charge a platform fee on per-signup payouts?
- A small platform fee is applied on wallet top-ups (shown at checkout). The Stripe Connect transfer to the ambassador is full-amount.
- What if my SaaS is single-page (Next.js, React Router)?
- The tag captures the handle on initial load. You only need to fire
raws('event', 'conversion', ...)when the conversion happens — no extra hooks for client-side routing.