--- title: OneHazel Affiliate Tag description: One-line install that captures affiliate tracking parameters from your landing URL into a first-party cookie, so your existing player ingest payload can include them automatically. --- # OneHazel Affiliate Tag ::: tip 30-second install ```html ``` Paste this on your landing / registration page. That's it. ::: ## Why you need it Every affiliate platform — ReferOn, Affise, Income Access, and the rest — sends players to your site via a tracking link with attribution parameters in the URL: ``` https://casino.example.com/?mid=78545_589097&fluid=112e2f77-ab8f-4954-9051-2a6854eddbc0 ``` If those parameters aren't captured at landing **and** included in the player registration event you send to OneHazel, the affiliate platform's daily reports will show empty attribution columns and they will reject the reports as malformed. Affiliates won't get paid. Your integration breaks silently. The OneHazel Affiliate Tag is the cheapest way to fix this: - One ``), then add a proxy route on your existing Express app: ```js const fetch = require('node-fetch'); // or built-in fetch on Node 18+ // Server-side proxy. Your client POSTs here; you forward to OneHazel. app.post('/api/onehazel/entities', requireSession, async (req, res) => { const upstream = await fetch('https://api.onehazel.com/operator-data-api/entities', { method: 'POST', headers: { // Server-only env var. Never reaches the browser. Authorization: `Bearer ${process.env.ONEHAZEL_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(req.body), }); const body = await upstream.json().catch(() => ({})); res.status(upstream.status).json(body); }); ``` In your registration page's client JS, POST to `/api/onehazel/entities` after reading `window.OneHazel.getAttribution()`. If you already create the player in a single server-side `POST /register` handler, you can skip the browser → proxy hop entirely and call OneHazel directly from `/register` — just make sure the URL params are passed through (e.g. via a hidden form field populated from `window.OneHazel.getAttribution()` at submit time). ### Django / Rails Drop the tag in your application layout (`base.html` / `application.html.erb`). On form submit, your client JS reads `window.OneHazel.getAttribution()` and includes the values in the POST to your server. Your server-side handler then forwards to OneHazel with the API key attached: ```python # Django — settings.py: # ONEHAZEL_API_KEY = os.environ["ONEHAZEL_API_KEY"] # server-only env var import os import requests from django.conf import settings def register_view(request): form = PlayerForm(request.POST) player = Player.objects.create(...) requests.post( "https://api.onehazel.com/operator-data-api/entities", headers={ # API key lives in settings.py / env, never in templates. "Authorization": f"Bearer {settings.ONEHAZEL_API_KEY}", "Content-Type": "application/json", }, json={ "entityType": "player", "externalId": str(player.id), "data": { "username": form.cleaned_data["username"], "mid": request.POST.get("mid"), "fluid": request.POST.get("fluid"), }, }, timeout=5, ) return JsonResponse({"ok": True}) ``` ```ruby # Rails — config/initializers/onehazel.rb: # Rails.application.config.onehazel_api_key = ENV.fetch("ONEHAZEL_API_KEY") class RegistrationsController < ApplicationController def create player = Player.create!(...) Net::HTTP.post( URI("https://api.onehazel.com/operator-data-api/entities"), { entityType: "player", externalId: player.id.to_s, data: { username: params[:username], mid: params[:mid], fluid: params[:fluid], }, }.to_json, { "Authorization" => "Bearer #{Rails.application.config.onehazel_api_key}", "Content-Type" => "application/json", }, ) render json: { ok: true } end end ``` ### Sanity check — no real affiliate click needed Visit `https://your-casino.example/?mid=78545_589097&fluid=112e2f77-ab8f-4954-9051-2a6854eddbc0` in a clean browser. Open DevTools → Application → Cookies. You should see: - Cookie name: `oh_attr_op_xxxxxxxx` - Value: URL-encoded JSON with `params`, `sources`, `firstSeenAt`, `referrer`, `landingPath` In the console, type `window.OneHazel.getAttribution()` and confirm the captured fields are present. If that works, the tag is wired. Register a fake player and confirm the entity ingest call lands on OneHazel's side (visible in the Connections / Activity view). ## Security: where to put the API key The Affiliate Tag is **client-side by design** — it has to read URL params in the browser at landing time, before any of your backend code runs. But everything *after* the tag — the call into `api.onehazel.com/operator-data-api/entities` — belongs on the server. That call carries your `oh_live_*` API key, which is a secret with quota, billing, and write access to your tenant. ### What goes in the browser - The ` ``` What gets sent: | Field | Source | |---|---| | `operator_id` | Your `data-operator-id` (public, like a GA measurement ID) | | `anon_id` | A browser-issued UUID stored alongside the attribution cookie (`oh_anon_`) | | `captured.params` | The URL params recognised from the registry (table above) | | `captured.sources` | The source slugs (e.g. `referon`, `utm`) | | `first_seen_at` | Timestamp the tag captured (ISO8601) | | `referrer` / `landing_path` | The browser-reported `document.referrer` + URL path | What does **NOT** get sent: - **Raw IP.** The server hashes inbound `x-forwarded-for` with SHA-256 and stores only the hash for fraud detection. The plaintext IP is never persisted. - **Cookies from other domains.** Same-origin, first-party only. - **Any other PII.** The tag has no access to player names, emails, or form fields. It only reads URL params it recognises. The server-sync POST is fire-and-forget — failures don't affect cookie behaviour or your registration handler. If you turn it off later (delete the attribute) the cookie path keeps working unchanged. ## Privacy - **First-party cookie only.** Set on your own domain. No third-party tracking. - **No fingerprinting.** The tag does not read device, browser, or hardware properties. - **`SameSite=Lax`** — available on top-level navigation (the registration flow), blocked from cross-site iframe leakage. - **90-day TTL.** Re-capture happens automatically if a player returns via a fresh affiliate click after expiry. - **Network calls only when you opt in to server sync.** With `data-server-sync="true"` the tag POSTs to `api.onehazel.com/track-event/v1/event` after capture. Without it, the tag is silent. See above for exactly what's sent. First-party attribution cookies are generally lawful under "legitimate interest" or "strictly necessary for the service the user requested" carve-outs in EU/UK/US privacy regimes. Confirm with your own counsel for your jurisdiction. ## Local development only — never ship to production ::: danger Local-dev quick-test only — never deploy this When you're poking at the tag from a local browser console and want to confirm the end-to-end flow without standing up a proxy route first, you can call OneHazel directly with a temporary key: ```js // LOCAL DEV ONLY — paste in DevTools console, never check this in. const attr = window.OneHazel.getAttribution(); await fetch('https://api.onehazel.com/operator-data-api/entities', { method: 'POST', headers: { Authorization: 'Bearer oh_live_PASTE_TEMP_KEY_HERE', 'Content-Type': 'application/json', }, body: JSON.stringify({ entityType: 'player', externalId: 'test_player_' + Date.now(), data: { mid: attr.params.mid, fluid: attr.params.fluid }, }), }); ``` **Do not check this into a file the browser will load.** Do not write it into a Next.js page, a Vite component, an inline `