internal commission tracker, and
none. The internal provider works with any payment provider because it
reads the normalized payment events vibestrap already writes — no extra
integration needed.
Prerequisites
- For SaaS providers: an account with the provider and the public API key / program ID from their dashboard.
- For internal: nothing. The
affiliate_referralandaffiliate_commissiontables are already in the schema (src/db/affiliate.schema.ts) and migrate withpnpm db:push. - A payment provider already configured (Stripe, Paddle, Lemon Squeezy, or Creem).
Step-by-step (internal)
-
Enable the internal provider in
src/config/site.ts: -
Push the schema if you haven’t already — the affiliate tables ship in
the default schema:
-
That’s it. The flow is automatic:
- A visitor lands on
?ref=CODE→ middleware sets thevbs_refcookie. - They sign up →
recordSignupReferral(userId)writes a row toaffiliate_referral(idempotent onuserId). - They pay → the payment webhook calls
recordCommission()which reads the referral and inserts anaffiliate_commissionrow at your default %.
- A visitor lands on
-
Build a payout dashboard — vibestrap doesn’t ship one. Query
affiliate_commission WHERE status = 'pending', group byreferrer_code, pay out via Stripe Connect / wire / whatever, thenUPDATE … SET status = 'paid', paid_at = now().
Step-by-step (SaaS providers)
-
Pick a provider in
src/config/site.ts: -
Set the matching env var in
.env.local: - Connect the provider to your payment gateway in their dashboard (Stripe Connect for Rewardful/Tolt, Paddle integration for Affonso, etc). The provider reads payment events directly from the gateway — vibestrap doesn’t relay anything for SaaS providers.
-
Restart
pnpm devto pick up the public env var.
Pick the right provider
| Provider | When to pick |
|---|---|
| internal | You want zero ongoing fees, full control over commission logic, and your data in your DB. Ships with Creem/Lemon out of the box. |
| Affonso | Solid Stripe + Paddle integration, lifetime tracking, low monthly cost. |
| Rewardful | Most polished Stripe-native option. Pick if you want a managed dashboard for your affiliates. |
| Tolt | SaaS-focused, leaderboards, recurring commissions. Good Stripe + LemonSqueezy support. |
Verify it works (internal)
- Open the site at
https://your.app/?ref=alicein incognito. - In devtools → Application → Cookies, confirm
vbs_ref=alice, expires in 60 days. - Sign up. In your DB:
- Buy something. Then:
You should see one row with
commission_cents = amount_cents * 0.20.
Common pitfalls
- Safari ITP blocks cookies. First-party cookies set by your own server
on a
?ref=landing are fine, but if you use cross-domain redirects (e.g. landing on a marketing subdomain → app subdomain) the cookie will be dropped. Keep the referral landing on the same eTLD+1 as the signup. - Refunds don’t reverse commissions. v0.1 doesn’t auto-reverse — you
need to manually insert a refund row (negative
commission_cents, statuscancelled) orUPDATE … SET status = 'cancelled'in your payout query. - Self-referrals. Nothing blocks a user from setting their own code as
the referrer. Add a guard in
recordCommissionif this matters:if (referral.userId === input.userId) return;. - Switching from internal to a SaaS provider. The SaaS providers track
on their own backend — historical commissions in
affiliate_commissionstay where they are; only new payments will be tracked by the SaaS provider. - Cookie name collision. If your buyers add their own analytics that
also uses
vbs_*prefixed cookies, changereferralCookieto something unique to your brand.
Official docs
- Affonso: affonso.io/docs
- Rewardful: help.rewardful.com
- Tolt: docs.tolt.io
- Internal schema:
src/db/affiliate.schema.ts - Internal helpers:
src/affiliate/track.ts