Skip to content

Data model

OpenPartner is event-sourced. Three layers are immutable raw inputs; the rest are derived views or ledgers.

Immutable means rows aren’t edited in place. They’re still deleted when a brand closes its account (after a 30-day grace period), and a person’s identities, events and clicks can be deleted on request.

Raw tables (immutable)

Click

Every click on a partner link.

FieldTypeNotes
clickIdULIDPrimary key.
partnerIdULIDWhich partner gets credit.
campaignstring?Optional grouping.
landingUrltextWhere the user landed.
referertext?HTTP referrer.
ipHashtextTruncated SHA-256 of the visitor’s IP. Raw IPs aren’t stored in the database, but the hash isn’t salted, so treat it as personal data.
uatextUser agent.
utm*text?utm_source, utm_medium, utm_campaign, utm_term, utm_content from the link.
countrytext?Visitor’s country, when the edge provides it.
fraudFlagtext?velocity, manual or revoked when a click is flagged.
createdAttimestamptzClick time.

Event

Conversion events from the SDK or server-side.

FieldTypeNotes
eventIdULIDPrimary key.
userIdstringCustomer’s user identifier.
typestringEvent name.
amount / currencyint / stringMoney in minor units.
metadatajsonbFree-form.
occurredAttimestamptzEvent time.

Derived (re-runnable from raw)

Identity

Bridge between a userId and the clickId they came from. Written when op.identify() is called and there’s an active click cookie.

Attribution

Result of running the attribution model over raw data. References the event and the click(s) credited. Re-derivable.

Ledger (immutable, audit-grade)

Commission

Computed from Attribution + program rules. Never updated in place — corrections are written as new rows referencing the original.

FieldTypeNotes
sourceCommissionIdULID?Set on a recruiter commission (two-tier): the recruit’s commission it’s a share of. Null on every other commission.

A recruiter commission sits on the recruit’s Attribution, with partnerId the recruiter. So a commission’s partner isn’t always its attribution’s partner.

Payout

Stripe Connect transfer record. Never updated — refunds and reversals are new rows.

Recruiting (two-tier commissions)

Who recruited whom, and what a program pays for it, live on the Partner and Program rows. Recruitment isn’t derived from clicks or events, so exports carry these columns as they are.

TableFieldTypeNotes
PartnerrecruitedByPartnerIdULID?The partner who recruited this one. Null when nobody did.
PartnerrecruitedAttimestamptz?When the recruitment started. Set exactly when recruitedByPartnerId is. Only events from then on pay the recruiter.
PartnerrecruitCodestring?The partner’s own code for their join-page link (?ref=). Unique per account.
ProgramrecruiterCommissionPercentdecimal?The recruiter’s share, as a percent of the recruit’s commission (10.00). Above 0, at most 50. Null = off, the default.
ProgramrecruiterCommissionMonthsint?How many months after recruitedAt the recruiter keeps earning. 1–120; null = no limit.
CommissionsourceCommissionIdULID?See Commission.

A partner can’t be their own recruiter, and two partners can’t recruit each other.

Why this matters

You can’t lose attribution history because the raw data is intact. You can audit any commission back to its source click. And exports carry both layers: an import restores attributions and commissions as they were stored, and the raw clicks, identities and events travel with them, so another system can re-derive attribution from the raw layers.