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, Creem, or NOWPayments).
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 Affonso/Rewardful). Both SaaS providers are Stripe-only — they read payment events directly from Stripe and Vibestrap doesn’t relay anything for them.
-
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. Works with any payment provider — pick this if you’re on Creem or NOWPayments. |
| Affonso | Stripe-only. Lifetime tracking, low monthly cost. |
| Rewardful | Stripe-only. Most polished Stripe-native option. Pick if you want a managed dashboard for your affiliates. |
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.
Going beyond
vibestrap stops at “wires connected, ready to extend” — by design. The internal provider gives you data; how you turn it into a product is yours to shape:- Payout admin UI — query
affiliate_commissiondirectly while you have under five affiliates; build a/adminpage when manual SQL hurts. - Affiliate self-service dashboard — they ask for it before you need to build it. A copy-able referral link is enough at first.
- Auto payouts — Stripe Connect, PayPal Mass Pay, Wise — pick when you have a monthly-or-tighter cadence.
recordSignupReferral,
recordCommission, and the two affiliate tables. Build on top —
don’t rewrite.
Official docs
- Affonso: affonso.io/docs
- Rewardful: help.rewardful.com
- Internal schema:
src/db/affiliate.schema.ts - Internal helpers:
src/affiliate/track.ts