SaaS Pro Max
Sign inStart free
Documentation menu

Partners

An affiliate program on top of the Revenue module. Partners are your application's own users; they earn a share of what the customers they refer pay, and they see their numbers inside your application, which reads them server-to-server with a partner key. SaaS Pro Max holds the ledger, runs the payouts and gives operators the admin view. Nothing here signs a partner in.

  • Requires the Revenue module with a billing connector that carries your own user id on each customer (Polar's external_id), because commissions are matched to referrals by that id.
  • Permissions: revenue.read to view, revenue.write to change the program, decide applications and draft payout batches. Credentials, voids, adjustments, sanctions decisions, exports and sending a batch for approval also need a passkey verified in the last ten minutes.
  • Paying a batch always needs a second operator with approvals.decide, in Operations. The person who sent the batch cannot approve it.
  • Partner emails are personal data and are masked unless the member also holds people.pii.read.

How money is derived#

Text
partner link click  ->  your signup  ->  referral (locked)  ->  billing payment  ->  commission  ->  payout
   /r/<slug>             POST /referrals      partner_referrals     revenue_payments     partner_commissions   partner_payouts
  • A referral locks a customer (customer_app_user_id) to one partner the first time it is recorded. A partner can never refer themselves.
  • A payment earns a commission when its customer is locked to a partner, the customer had not paid before the referral, and the payment falls inside the window that opens with the customer's first payment (12 months by default). The base is the pre-tax net_cents; the rate is the base rate, or the tier rate once the partner has the tier's number of paying customers.
  • A partner's discount code at the first checkout beats a link: the order's discount_id reassigns an unpaid referral to that code's partner.
  • A commission is pending for the hold (45 days by default), then approved (payable). A refund adds a reversal for the newly refunded share; once the earning was paid, the reversal is a negative balance against the next payout.
  • Small approved balances that have not grown for the inactivity period expire.
  • When a referred customer first pays, the referral is flagged same_email or same_email_domain if the customer's billing email matches the partner's, or shares a company domain (common mail providers excluded). A flag never blocks a commission; an operator reviews it and voids the commission if it is a self-referral. A referral that names a click the partner never received is flagged unknown_click.

Every commission row names the payment that produced it, so the ledger can be replayed from the billing provider's signed payments. partners.commissions.sync walks payments changed since its cursor every ten minutes; the unique key on (payment_id, kind, refund_level_cents) makes the replay harmless.

Partner API#

All calls carry Authorization: Bearer spm_ptn_..., the program's partner key, minted under Partners -> Settings. The key names the program, so a request can only touch the application that minted it. Never send it from a browser.

Method and pathBody or queryAnswer
POST /api/v1/partners/applicationsappUserId, email, name?, country?, code, pitch?partnerId, status, created
POST /api/v1/partners/referralscustomerAppUserId, referrerAppUserId? or code?, clickId?attributed, or the reason it was not
GET /api/v1/partners/summary?user=<appUserId>Terms, status, links, code, stats, balances, payout blockers, recent commissions and payouts. Names no customer.
PUT /api/v1/partners/payout-methodrail: paypal (email) / wise (email, accountHolderName) / stripe (returnUrl)Masked label; for Stripe a single-use onboarding URL
PUT /api/v1/partners/tax-formform: w9 / w8ben / w8bene and its fieldsStored encrypted
GET /api/v1/partners/discount?referrer=<appUserId> or ?code=The partner's discount id, for pre-applying it at checkout

@saaspro/node wraps these calls as PartnerClient (see the SDK README), so another application can add a partner page without writing the HTTP layer.

/r/<slug> is the partner's short link. It records the click (keyed IP hash and referrer domain) and redirects to the landing page with ?ref=<code>&rc=<click id>. Your application keeps the code in its own cookie and sends both values with the referral.

Payouts#

On the program's payout day partners.batches.build drafts one batch: one payout per partner whose payable balance clears the threshold and who has nothing blocking a payout (not_approved, no_payout_method, no_tax_form, payout_method_frozen for seven days after a change, sanctions_review). Building reserves the commissions; no money moves.

An operator sends the draft for approval; a second operator approves it in Operations; the batch is then paid on each partner's rail:

RailAPIWithout credentials
PayPal PayoutsOne API batch, sender_batch_id is the duplicate guard; item status is followed until SUCCESSDownload the PayPal file, upload it, then Mark paid
WiseQuote for the exact target amount (the program pays Wise's fee), email recipient, transfer, fund from balance; customerTransactionId is the payout id. A profile in the UK or EEA, or a funding SCA challenge, leaves the transfer for funding in the Wise appDownload the Wise batch file, then Mark paid
Stripe Global PayoutsRecipient account with hosted onboarding, then an outbound payment idempotent on the payout id. US and UK businesses only; the preview API version is pinned and overridable with SPM_STRIPE_PAYOUTS_VERSIONPay from the Stripe dashboard, then Mark paid

A failed payout returns its commissions to the payable balance for the next batch.

Screening and tax#

  • Partners about to be paid are screened weekly against Treasury's SDN list (partners.sanctions.refresh downloads SDN.CSV and ALT.CSV). An embargoed country blocks payouts; a name match or a country with a partial program sends the partner to review, and only an operator clears or blocks them.
  • Every partner submits a W-9 (US persons) or W-8BEN / W-8BEN-E before their first payout. The form is stored in the vault.
  • Settings -> Tax reporting downloads Form 1099-NEC data for US partners paid $2,000 or more in a calendar year, with their W-9 details.

Coupons#

With a Polar token that holds discounts:write, approving a partner creates a Polar discount whose code is the partner code: the customer discount percentage, repeating for the configured months. The Revenue connector stays read-only; Partners keeps its own token.

Jobs#

KindEveryDoes
partners.commissions.sync10 minutesDerives commissions and reversals from changed payments
partners.holds.releasehourPending commissions past their hold become payable
partners.batches.buildhourDrafts the month's batch on the payout day
partners.batches.executeminutePays batches a second operator approved
partners.payouts.refresh30 minutesFollows sent payouts until they settle or fail
partners.balances.expiredayExpires small inactive balances
partners.sanctions.refreshweekReloads the SDN list
partners.discount.create, partners.emailon demandPolar discount on approval; partner emails