Tracking events
Events are how revenue (or any conversion you care about) enters OpenPartner. Each event references the user that triggered it; OpenPartner walks Identity → Click to determine attribution and compute commissions.
Events are server-side only — they drive money, so the browser never gets to assert
them. Report from your backend with the server SDK or a plain HTTP call, authenticated
with an API key (mint one in your dashboard under Admin → CRM integration; it carries
the events:write scope).
Standard events
| Event | Use for |
|---|---|
signup | User account creation. No revenue yet — still useful for funnel analysis. |
trial_started | Trial subscription started. |
subscription_created | Paid subscription created. |
invoice_paid | Recurring revenue. Most programs commission on this. |
Server SDK
import { OpenPartnerServer } from '@openpartner/sdk/server';
const op = new OpenPartnerServer({ apiUrl: process.env.OPENPARTNER_API_URL!, // same base URL as the browser SDK apiKey: process.env.OPENPARTNER_API_KEY!,});
// On signup:await op.trackEvent({ userId: user.id, type: 'signup' });
// On a paid invoice:await op.trackEvent({ userId: user.id, type: 'invoice_paid', value: 49.0, // decimal, MAJOR units — dollars, not cents currency: 'USD', metadata: { stripeInvoiceId: invoice.id },});userId must be the same identifier the browser SDK passed to identify() — that’s the
join key. value and currency are required for any event you commission a percentage
on. Runs on Node 18+, Bun, Deno, and Cloudflare Workers.
Prefer raw HTTP? The equivalent call is
POST /attribution/events — same fields, same response.
The response tells you what happened
{ ok: true, eventId: '01K…', attribution: { status: 'attributed', // or 'no_identity' / 'no_click' / 'outside_window' model: 'last_click', touches: [{ clickId, partnerId, weight: 1, attributionId, commissionId }], }}no_identity means nobody ever called identify() for that user; no_click means the
user was identified but didn’t arrive through a partner link. Multi-touch models
(linear, position) return one touch per attributed click with fractional weights.
Custom events
Any event name works:
await op.trackEvent({ userId: user.id, type: 'demo_booked', value: 500, currency: 'USD' });Then target the name in a program’s commission rule in the admin UI.
If you bill with Stripe
Keep your Stripe webhooks pointed at your own backend and add one trackEvent call where
you already handle invoice.paid — that’s the whole integration, and it works identically
if you ever switch billing providers.
Self-hosted installs have an additional option: OpenPartner’s own /webhooks/stripe
endpoint can ingest customer.created, customer.subscription.created, and
invoice.paid directly (set STRIPE_WEBHOOK_SECRET and put your OpenPartner user ID in
the Stripe customer’s openpartner_user_id metadata). On the hosted platform, use
trackEvent from your own webhook handler instead.
Error handling
Failures throw an OpenPartnerError with status (HTTP), code (machine-readable
string, e.g. invalid_body), and detail (the full response body):
try { await op.trackEvent({ userId, type: 'signup' });} catch (err) { if (err instanceof OpenPartnerError) console.error(err.status, err.code, err.detail);}