Appearance
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
| 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- Attribution — captured in the browser by the Attribution Tag (
oh.js) at registration, carrying themid/fluidfrom 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. - 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).
- 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") |
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 (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 — 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. 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, anddataare required (missing →400).externalIdis the upsert key and becomes ReferOn'sCustomerID(OneHazel maintains a stableexternalId → integermap; 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+fluidhere 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 byexternalIdand 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 movement — transaction.created (feeds Deposits / Withdrawals / NetCash):
POST https://api.onehazel.com/operator-data-api/entities/<externalId>/eventsjson
{
"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.
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:
- 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. - A read API (e.g.
GET /…/players,…/transactions,…/roundswith anupdated_sincefilter) so OneHazel can resolve ids and backfill. OneHazel consumes this; it does not poll it on a schedule. - A read-scoped key for that API.
You then:
- Give OneHazel your read key (so it can resolve your internal player id → your
externalIdwhen 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
- Registration — open your storefront via an affiliate link (
?mid=…&fluid=<uuid>) and register a player. OneHazel should create an attributed entity. - 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 showsucceeded). The next/reportcycle uploads the activity to ReferOn. - 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
| 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 401s | 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 /
brandGuidwired, report types enabled - [ ] ReferOn: affiliate id, report types, vocab, and mid/fluid shapes confirmed
- [ ] Verified: registration (attributed) → activity (report) → customer status