Quickstart
The fastest path runs Postgres in Docker and the router, API and portal from source.
1. Clone and start
git clone https://github.com/getcoherence/openpartner.gitcd openpartnercp .env.example .env# In .env, set ADMIN_API_KEY and SECRETS_ENCRYPTION_KEY to random 32-byte hex values:# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"pnpm installdocker compose up -d postgrespnpm migrateThen start each service in its own terminal:
pnpm dev:api # API on :4601pnpm dev:router # click router on :4701pnpm dev:portal # portal on :5673- Portal: http://localhost:5673
- API: http://localhost:4601
- Click router: http://localhost:4701
2. First-run install
Open http://localhost:5673/install. The install wizard asks for:
- You — your name + admin email.
- Program — the public name partners will see, plus a support email.
- Email delivery — Postmark or SMTP for sending magic links, or none, which prints the
links to the
pnpm dev:apiconsole.
Once submitted, the install wizard creates the first admin and emails you a magic link.
3. Add a partner
From the admin dashboard, Partners → Invite partner. Drop in their email; they’ll get a magic link to claim the account.
4. Drop the SDK on your site
npm install @openpartner/sdkIn the browser, initialize it once per page load. It captures the referral from the
?cref= the click router adds to your URL:
import { OpenPartner } from '@openpartner/sdk';
export const op = OpenPartner.init({ apiUrl: 'http://localhost:4601' });When a user signs up, call identify() with your own user id:
await op.identify(user.id);Bill with Stripe? You don’t need trackEvent for Stripe payments. Set
STRIPE_SECRET_KEY in .env to your Stripe key and point a Stripe webhook at the API’s
/webhooks/stripe: checkouts, invoices, renewals and refunds are then recorded from Stripe.
Locally, stripe listen --forward-to localhost:4601/webhooks/stripe does it, and prints
the signing secret to put in STRIPE_WEBHOOK_SECRET_PLATFORM. The full setup is in
Stripe Billing. (On the hosted plan, brands
connect Stripe from Admin → Integrations instead.)
For revenue that isn’t a Stripe payment, report it from your server with trackEvent:
import { OpenPartnerServer } from '@openpartner/sdk/server';
const opServer = new OpenPartnerServer({ apiUrl: 'http://localhost:4601', apiKey: process.env.ADMIN_API_KEY!,});
await opServer.trackEvent({ userId: user.id, type: 'invoice_paid', value: 49.0, // major units — dollars, not cents currency: 'USD', externalEventId: order.id, // your id for this payment: makes retries safe});The SDK ships ESM + CJS bundles, with types for both, and works with any modern bundler (Vite, webpack, esbuild, Next.js, etc.). See Tracking events.
5. Approve commissions and pay out
Commissions arrive as accrued and wait in Admin → Review queue, where you approve or
reverse them. If the program has a holdback, they’re approved automatically once it ends.
Approved commissions become payable on the next payout run.
For Stripe Connect payouts, partners onboard through the portal — their account, their funds, your transfers.