Raws.dev

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:

  1. An ambassador shares their personal referral link raws.dev/r/<project>/<handle>.
  2. The visitor lands on your SaaS via the redirect with utm_source=raws&utm_campaign=<handle> appended.
  3. The visitor signs up. You fire raws('event', 'conversion', { external_id }).
  4. 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).

What the tag does on every pageview:
  • Reads utm_source and utm_campaign from the URL.
  • If utm_source=raws, stores the campaign value (the ambassador handle) in localStorage under raws_ambassador with 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 v4 generated 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.

  1. Embed the JS tag and your conversion event call.
  2. Visit your own SaaS via https://raws.dev/r/<project-slug>/<your-handle>, complete the signup flow.
  3. Open the project's ambassador hub. The "Recent fires" feed polls every 10s — your event appears with status test.
  4. Click Activate payouts. From now on, real conversions debit your wallet and pay ambassadors.
  5. Need to debug later? Hit Pause — incoming events keep landing as test until 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.

Token rotation: the project token is exposed in your client HTML. If you suspect abuse (someone fires fake conversions to drain your wallet), regenerate it from the hub. The conversion endpoint is rate-limited to 60 fires/min per (token, IP), and dedupe on 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 — check connect-src in your CSP.
Status is test even 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 pending and 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.