Skip to content
For LLMsView as Markdown·

ReferOn for Casinos

Send your casino's player, activity, and compliance data to the ReferOn affiliate network through OneHazel. You never call ReferOn directly and you never build a ReferOn-shaped payload: you expose your data in your own native shape (or push it to OneHazel's ingest API), and OneHazel maps it to ReferOn's reports and postbacks.

This is a config + provisioning integration. Depending on the path you choose, it can be zero-code on your side.

Who does what

PartyOwns
You (operator)Sending player + activity + compliance data, and the attribution that ties a player to an affiliate.
OneHazelIngesting your data, mapping it to ReferOn's vocabulary, and producing ReferOn's registration / activity / customer-status reports and real-time postbacks.
ReferOnThe affiliate account: your affiliate id, which report types are enabled, and the field shapes/vocabulary.

You talk to OneHazel. OneHazel talks to ReferOn. You expose native data; OneHazel translates.

How the data flows

Three kinds of data reach OneHazel, and one of them (attribution) is what actually ties a player to an affiliate:

                 ┌─ registration attribution (mid/fluid) ──┐
  browser + tag ─┤  Attribution Tag → entities upsert       ├─► OneHazel ─► ReferOn
                 └──────────────────────────────────────────┘   (maps +
  player + activity ── push OR native consume ──────────────►    reports +
   registrations · deposits · bets · wins · balances ·           postbacks)
   account/compliance status
  1. Attribution — captured in the browser by the Attribution Tag (oh.js) at registration, carrying the mid/fluid from the affiliate landing link. This is the only thing that links a player to an affiliate. Without it, the player is unattributed and dropped from ReferOn's reports.
  2. Player + activity data — registrations, deposits, withdrawals, bets, wins, balances. Sent to OneHazel either by pushing to the Operator Data API (Path A) or by OneHazel consuming your platform's native webhooks/REST (Path B).
  3. Compliance / account status — duplicate, self-exclusion, fraud, account open/closed. Drives ReferOn's customer-status report.

What ReferOn needs from you

ReferOn reportWhat it needsWhere it comes from
RegistrationOne row per new player, attributed to an affiliatePlayer entity + mid/fluid
ActivityDaily aggregates per player: deposits, withdrawals, turnover, GGR/NGRTransaction / bet / win events
Customer statusUpdates to an existing customer's compliance stateAccount/compliance status changes

The mid / fluid shapes — get these exactly right

These arrive on the affiliate landing URL (?mid=…&fluid=…) and must match ReferOn's expectations, or the row is rejected or silently dropped.

FieldShape✅ Good❌ Bad
mid{affiliateId}_{clickId}, affiliateId all digits373283_2158191abc_123 (non-numeric prefix → dropped) · 373283 (no clickId)
fluidbare lowercase UUID, exactly 36 chars, no prefix63414158-65af-4039-94d5-c12394f1e487fl_6341… (39 chars → ReferOn 400 "fluid must be ≤ 36 characters")

mid + fluid travel together

Both are required and must travel together. A player missing either is skipped from ReferOn's registration and activity reports. They are not enforced by the ingest API — a player without them ingests fine (200) but is silently unattributed. Always include both at registration.

Step 1 — Capture attribution

Attribution is the same for both data paths. Two options:

Add the Attribution Tag (oh.js) to your storefront. It captures mid/fluid from the affiliate landing link in the browser and upserts the attributed player entity for you — no server code.

Build-time gotcha for Next.js storefronts

If your storefront is Next.js, the public operator id is inlined into the page at build time (NEXT_PUBLIC_* vars), not at runtime. Setting it only in your runtime environment does nothing — the tag silently disappears and no attribution is captured. Pass it as a Docker build arg and rebuild the image. See the Attribution Tag page for the exact variable names and snippet.

Verify the affiliate query survives your redirects

If your storefront does a locale redirect (e.g. //en), confirm it preserves ?mid&fluid:

bash
curl -sL -o /dev/null -w '%{url_effective}\n' \
  'https://<your-storefront>/?mid=373283_2158191&fluid=63414158-65af-4039-94d5-c12394f1e487'
#  → must end at /en?mid=373283_2158191&fluid=63414158-...  (NOT a bare /en)

A redirect that strips the query breaks attribution for every affiliate landing.

Option 2: server-side attribution

If you terminate the affiliate landing on your own backend and already hold mid/fluid, skip the tag and send them on the player entity yourself (Path A, in the entity data). This is the only attribution path if you have no browser tag.

Step 2 — Choose how you send player + activity data

Two paths. Pick the one that matches your platform; both are first-class.

Path A — PushPath B — Native consume
You doCall OneHazel's Operator Data APIExpose your platform's native signed webhooks + read API
OneHazel doesIngests directlyRuns a source-connector that ingests + maps your native shape
Best forAny platform; full control of the pushPlatforms that already emit HMAC-signed webhooks
Code on your sideYour own outbound callNone, if your platform already emits the events

Path A — Push to the Operator Data API

You push players and activity to OneHazel's Operator Data API. Authenticate with your server-only operator key.

POST https://api.onehazel.com/operator-data-api/entities
Authorization: Bearer oh_live_…
Content-Type: application/json
Idempotency-Key: <optional — makes a retried POST safe>

Push a player (registration)

json
{
  "entityType": "player",
  "externalId": "<your stable player id>",
  "data": {
    "username":          "keith1",
    "email":             "player@example.com",
    "country":           "MT",
    "currency":          "EUR",
    "registration_date": "2026-06-04T13:53:00Z",
    "date_of_birth":     "1992-03-14",
    "gender":            "M",
    "registration_ip":   "84.121.45.10",
    "mid":               "373283_2158191",
    "fluid":             "63414158-65af-4039-94d5-c12394f1e487"
  }
}
  • entityType, externalId, and data are required (missing → 400).
  • externalId is the upsert key and becomes ReferOn's CustomerID (OneHazel maintains a stable externalId → integer map; just send your own stable id).
  • Inside data, OneHazel enforces only what your entity template marks required; everything else is stored as-is. registration_date (UTC) drives which report day the row lands in.
  • Include mid + fluid here for the player to appear in ReferOn (see the shape rules above). If you capture attribution with the tag instead, OneHazel joins it to the player by externalId and you can omit them.

Re-POSTing a player REPLACES data — it does not merge

The entity upsert does a full data replace keyed on externalId. Re-posting the same player without mid/fluid wipes them. Always send the complete record (including mid/fluid) on every POST, or POST each player exactly once. Use Idempotency-Key to make a retry safe.

Push activity (deposits, withdrawals, bets, wins)

Activity is pushed as events on the player entity. OneHazel's activity report materialises two specific event types — send these exact names or the event is stored but never reaches the report.

Money movementtransaction.created (feeds Deposits / Withdrawals / NetCash):

POST https://api.onehazel.com/operator-data-api/entities/<externalId>/events
json
{
  "eventType": "transaction.created",
  "data": {
    "type":        "deposit",
    "amount":      50.00,
    "currency":    "EUR",
    "occurred_at": "2026-06-04T14:10:00Z"
  }
}

data.type is deposit or withdrawal; the first deposit sets the player's first-deposit date (FDD).

Gameplayround.played, one per settled round (feeds GGR / NGR / Turnover):

json
{
  "eventType": "round.played",
  "data": {
    "bet":         2.00,
    "win":         0.00,
    "product_id":  1,
    "occurred_at": "2026-06-04T14:11:00Z"
  }
}

product_id maps to ReferOn's ProductID: 1 = casino, 2 = sport. occurred_at (UTC) buckets each event into the right report day; OneHazel aggregates per player per day into ReferOn's activity columns — you send raw events, OneHazel does the math.

The activity event type must be exact

Only transaction.created and round.played are read by the activity report. An event pushed under any other name (e.g. a bare transaction) is stored on the entity but silently never appears in ReferOn's activity report.

Batch and limits

For backfill, use the batch endpoints (/batch/entities, /batch/events) and Idempotency-Key (7-day TTL). For current item caps and rate-limit buckets (ingest:realtime / ingest:batch), see the Operator Data API reference — it is the source of truth.


Path B — OneHazel consumes your native events

If your platform already emits HMAC-signed webhooks and a read REST API, you don't push anything ReferOn- or OneHazel-shaped. You expose your data in your own native shape and OneHazel runs a source-connector that ingests it, verifies your signature, and maps it to ReferOn. This is the consume-native model — OneHazel onboards your platform the way it onboards any third-party operator.

What OneHazel needs from your platform:

  1. Signed webhooks for the activity + compliance events (e.g. transaction.created, round.played, wallet.updated, and a status-change event), HMAC-signed so OneHazel can verify each delivery.
  2. A read API (e.g. GET /…/players, …/transactions, …/rounds with an updated_since filter) so OneHazel can resolve ids and backfill. OneHazel consumes this; it does not poll it on a schedule.
  3. A read-scoped key for that API.

You then:

  • Give OneHazel your read key (so it can resolve your internal player id → your externalId when an activity event arrives, attaching it to the attributed player instead of creating a stub).
  • Create a webhook subscription pointing at the OneHazel ingest URL for your operator, listing every event type you want delivered, and hand OneHazel the subscription's signing secret.

A subscription delivers only the event types you list

Most webhook dispatchers deliver only events whose type is in the subscription's event_types. An event type you forget to list is silently 0-delivered — and on many platforms the source row still shows succeeded (it "succeeded" at fanning out to zero subscribers). A succeeded status is not proof of delivery; check the subscription's deliveries. List all the activity + compliance event types up front, and don't include your registration event here if attribution already rides the tag — that would double-feed.

Reference implementation — the SIM Casino platform

The open SIM Casino reference platform implements Path B end-to-end and is a concrete model for what OneHazel consumes:

  • Events: transaction.created, round.played, wallet.updated, player.status_changed.
  • Each delivery is signed X-SimCasino-Signature: t=<unix>,v1=<hmac-sha256> over "<t>.<raw-body>", verified by OneHazel against the subscription secret.
  • A read surface at /operator/v1/{players,transactions,rounds,games} with ?updated_since= for backfill, authed by a read-scoped key.
  • Non-2xx deliveries retry with exponential backoff (cap 5 min, 8 attempts).

See the SIM Casino Operator API docs for the full native shapes and the HMAC verification snippets (Python / Node / Go).

Step 3 — Customer status (compliance)

When compliance staff change a player's account or compliance state (duplicate, self-exclusion, fraud, account closed), send that change to OneHazel — as an event (Path A) or a native status-change webhook (Path B). OneHazel writes a customer-status record and updates the customer at ReferOn.

ReferOn's customer-status vocabulary:

  • Status ∈ {OPEN, CLOSE, FROZEN}
  • boolean flags: IsDuplicated, etc.
  • FraudStatus ∈ {NOT_FRAUD, SUSPICIOUS, FRAUD}

customer-status is an UPDATE, not an insert

ReferOn can only update a customer it already has (one that delivered a registration). Flagging a brand-new or unknown player it has never seen will fail with a "cannot find customer" style error. Test with a player ReferOn already knows.

Delivery cadence

Customer-status is delivered by a daily bulk push — distinct from the pull-based registration/activity reports below. A status change you send today reaches ReferOn on the next daily cycle, not instantly.

Step 4 — Provisioning with OneHazel and ReferOn

These are confirmations, not code — coordinate them with your OneHazel and ReferOn contacts:

  • OneHazel registers your operator → brand → brandGuid, points its connector at your data source, and enables the report types (registration, activity, real-time postbacks, customer status).
  • ReferOn confirms your affiliate id, that the report types are enabled for your brand, the customer-status vocabulary, and the mid/fluid shapes.

Registration & activity reports are pull-based

ReferOn calls OneHazel's /report endpoint on a schedule (hourly / daily) and OneHazel uploads in response, landing inside ReferOn's acceptance window. An unsolicited push lands outside any window and is rejected as "marked as skipped" — that's expected, not a failure.

Step 5 — Verify end-to-end

  1. Registration — open your storefront via an affiliate link (?mid=…&fluid=<uuid>) and register a player. OneHazel should create an attributed entity.
  2. Activity — make a deposit and a few bets for that player. The events reach OneHazel (Path A: your POSTs return 2xx; Path B: the subscription's deliveries show succeeded). The next /report cycle uploads the activity to ReferOn.
  3. Customer status — flag the player (e.g. mark duplicated). OneHazel writes the customer-status record; the next daily push updates the customer at ReferOn.

Verify delivery at the destination, not the source

On Path B especially, a succeeded source row is not proof of delivery — confirm the subscription's delivery rows are succeeded for each event type.

Troubleshooting

SymptomLikely cause
New players have no mid/fluidAttribution Tag not loaded (Next.js build-arg gotcha), or landing redirect stripping ?mid&fluid
ReferOn 400 "fluid must be ≤ 36 characters"fluid has a prefix / isn't a bare 36-char UUID
Player dropped from reportsmid prefix non-numeric, or mid/fluid missing
Player's mid/fluid got wipedRe-POSTed the entity without them (full data replace — Path A)
Activity never appears in ReferOn's report (Path A)Event pushed under a name other than transaction.created / round.played
Event "succeeded" but never reached ReferOnEvent type not in the subscription's event_types (0-deliver — Path B)
Every webhook delivery 401sSigning-secret mismatch between you and OneHazel (Path B)
ReferOn "marked as skipped"Report type not enabled for the brand, or uploaded outside the /report window
customer-status "cannot find customer"Flagging a player ReferOn never received a registration for

Day-2 operations

  • Adding an event type to an existing subscription (Path B) — if your platform has no "update event_types" endpoint, recreate the subscription (which mints a new signing secret). Create the new subscription before deleting the old one so no event 0-delivers in the gap, then swap the secret with OneHazel.
  • Rotating a key or secret — coordinate the swap with OneHazel; deliveries that failed during the window backfill via retry.

Summary checklist

  • [ ] Attribution captured (tag with the build-time var set, or server-side mid/fluid); affiliate query survives storefront redirects
  • [ ] Player + activity data flowing via Path A (push) or Path B (native consume)
  • [ ] Customer-status changes sent to OneHazel
  • [ ] OneHazel: operator / brand / brandGuid wired, report types enabled
  • [ ] ReferOn: affiliate id, report types, vocab, and mid/fluid shapes confirmed
  • [ ] Verified: registration (attributed) → activity (report) → customer status