--- title: ReferOn for Casinos description: Light up the ReferOn affiliate integration for your casino through OneHazel — config and provisioning only, no ReferOn-specific code on your side. --- # 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. ::: tip Who does what | Party | Owns | |---|---| | **You (operator)** | Sending player + activity + compliance data, and the attribution that ties a player to an affiliate. | | **OneHazel** | Ingesting your data, mapping it to ReferOn's vocabulary, and producing ReferOn's registration / activity / customer-status reports and real-time postbacks. | | **ReferOn** | The 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](./attribution-tag.html) (`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](#path-a-push-to-the-operator-data-api)) or by **OneHazel consuming** your platform's native webhooks/REST ([Path B](#path-b-onehazel-consumes-your-native-events)). 3. **Compliance / account status** — duplicate, self-exclusion, fraud, account open/closed. Drives ReferOn's customer-status report. ## What ReferOn needs from you | ReferOn report | What it needs | Where it comes from | |---|---|---| | **Registration** | One row per new player, attributed to an affiliate | Player entity **+ `mid`/`fluid`** | | **Activity** | Daily aggregates per player: deposits, withdrawals, turnover, GGR/NGR | Transaction / bet / win events | | **Customer status** | Updates to an existing customer's compliance state | Account/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. | Field | Shape | ✅ Good | ❌ Bad | |---|---|---|---| | `mid` | `{affiliateId}_{clickId}`, **affiliateId all digits** | `373283_2158191` | `abc_123` (non-numeric prefix → dropped) · `373283` (no clickId) | | `fluid` | **bare lowercase UUID, exactly 36 chars, no prefix** | `63414158-65af-4039-94d5-c12394f1e487` | `fl_6341…` (39 chars → ReferOn `400 "fluid must be ≤ 36 characters"`) | ::: warning 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: ### Option 1 (recommended): the Attribution Tag Add the [Attribution Tag](./attribution-tag.html) (`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. ::: danger 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](./attribution-tag.html) page for the exact variable names and snippet. ::: ::: tip 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:///?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](#path-a-push-to-the-operator-data-api), 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 — Push | Path B — Native consume | |---|---|---| | **You do** | Call OneHazel's Operator Data API | Expose your platform's native signed webhooks + read API | | **OneHazel does** | Ingests directly | Runs a source-connector that ingests + maps your native shape | | **Best for** | Any platform; full control of the push | Platforms that already emit HMAC-signed webhooks | | **Code on your side** | Your own outbound call | None, 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](../api-reference/operator-data-api.html). 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: ``` ### Push a player (registration) ```json { "entityType": "player", "externalId": "", "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. ::: danger 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 movement** — `transaction.created` (feeds Deposits / Withdrawals / NetCash): ``` POST https://api.onehazel.com/operator-data-api/entities//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). **Gameplay** — `round.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. ::: warning 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](../api-reference/operator-data-api.html) — 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**. ::: warning 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. ::: ::: details 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=,v1=` over `"."`, 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](#path-a-push-to-the-operator-data-api)) or a native status-change webhook ([Path B](#path-b-onehazel-consumes-your-native-events)). 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}` ::: warning 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. ::: ::: tip 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. ::: tip 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=`) 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. ::: warning 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 | Symptom | Likely cause | |---|---| | New players have no `mid`/`fluid` | Attribution 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 reports | `mid` prefix non-numeric, or `mid`/`fluid` missing | | Player's `mid`/`fluid` got wiped | Re-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 ReferOn | Event type not in the subscription's `event_types` (0-deliver — Path B) | | Every webhook delivery `401`s | Signing-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