Skip to content

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

EventUse for
signupUser account creation. No revenue yet — still useful for funnel analysis.
trial_startedTrial subscription started.
subscription_createdPaid subscription created.
invoice_paidRecurring 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);
}