PAM IntegrationRecipes & scenarios

Recipes & scenarios

End-to-end examples for the situations a PAM integration actually hits. Each shows the raw event the PAM sends, the mapping that translates it, and the result ingestion produces. Test any of them in the live tester before going live.

Registration with player identity

The PAM registers a player and sends profile fields. Map identity onto player columns so the CRM record is populated on first contact.

Raw:

{
  "event_type": "PlayerRegistered",
  "player_id": "77120",
  "payload": {
    "email": "  Mario.Rossi@Example.IT ",
    "first": "Mario", "last": "Rossi",
    "country": "IT", "phone": "+39333...",
    "regDate": "2026-07-09T09:00:00Z"
  }
}

Mapping (excerpt):

{
  "eventTypeMap": { "PlayerRegistered": "registration" },
  "playerFieldMap": [
    { "from": "email", "to": "column:email",             "transform": "trim|lower" },
    { "from": "first", "to": "column:first_name" },
    { "from": "last",  "to": "column:last_name" },
    { "from": "phone", "to": "column:phone" },
    { "from": "regDate", "to": "column:registration_date", "transform": "date_iso" },
    { "from": "country", "to": "custom_field:country" }
  ]
}

Result: event registration; player upserted with a normalized email, name, phone, registration date, and metadata.custom.country. (country isn’t an allow-listed column, so it goes to a custom field.)

Deposit in minor units (cents)

Raw: { "event_type": "CashIn", "player_id": "77120", "payload": { "amt": 2500, "cur": "eur", "method": "card" } }

Mapping:

{
  "eventTypeMap": { "CashIn": "deposit" },
  "payloadFieldMap": {
    "deposit": [
      { "from": "amt",    "to": "amount",   "transform": "cents_to_units" },
      { "from": "cur",    "to": "currency", "transform": "currency" },
      { "from": "method", "to": "method" }
    ]
  }
}

Result: event deposit, derived { amount: 25, currency: "EUR", method: "card" } (raw kept under _raw). The worker folds this into the player’s deposit totals (total_deposits += 25, deposit_count += 1, last_deposit_at) — not into GGR (GGR is bets − wins). method powers “deposits by payment method”. See Canonical catalog → Financial derivation.

Withdrawal: request vs settlement

Distinguish the request from the money actually leaving. Map two PAM types to two canonical types:

"eventTypeMap": {
  "WithdrawRequested": "withdrawal_request",
  "WithdrawPaid":      "withdrawal"
}

Only withdrawal (settled) is netted into cash-flow; withdrawal_request is tracked for funnel/latency reporting.

Account status change

The PAM changes a player’s status; mirror it into the CRM.

Raw: { "event_type": "StatusChanged", "player_id": "77120", "payload": { "state": "S" } }

{
  "eventTypeMap": { "StatusChanged": "status_changed" },
  "payloadFieldMap": {
    "status_changed": [
      { "from": "state", "to": "status",
        "transform": "enum:{A:active,S:suspended,C:closed,X:self_excluded}" }
    ]
  }
}

Result: event status_changed with derived status: "suspended".

KYC approval

"eventTypeMap": { "KycApproved": "kyc_verified" }

Drives compliance/lifecycle views that key off a confirmed KYC.

Bonus granted by the operator

"eventTypeMap": { "FreeSpinGrant": "bonus_granted" },
"payloadFieldMap": {
  "bonus_granted": [
    { "from": "bonusValue", "to": "amount",   "transform": "number" },
    { "from": "ccy",        "to": "currency", "transform": "currency" }
  ]
}

Unknown / new event types

Say the PAM starts sending RouletteSpin you haven’t mapped. Behaviour depends on the unmapped policy:

  • store_raw_flag (default) — the event is stored and returned in the response’s unknown_event_types, so you notice and can add a mapping. Nothing is lost.
  • reject — the event is refused and reported in rejected_event_types.
  • ignore — the event is silently dropped.

You can also map it to a custom type to keep it without polluting the canonical set: "eventTypeMap": { "RouletteSpin": "custom.roulette_spin" }.

Batching for throughput

Send up to 100 events per request; run several requests concurrently for volume:

{ "events": [
  { "player_id": "77120", "event_type": "Login" },
  { "player_id": "77120", "event_type": "CashIn", "event_id": "d-1", "payload": { "amt": 2500 } },
  { "player_id": "88301", "event_type": "Bet",    "event_id": "b-9", "payload": { "amt": 500 } }
] }

Reliable delivery (retries)

Always attach an event_id. On any network error or 5xx, retry the same request — duplicates are de-duplicated on event_id, so a deposit is never double-counted:

POST /events           → (timeout, no response)
POST /events (same body, same event_id)  → 200 { stored: 0, deduped: 1 }  // safe

Backfill / replay

To load history, send the events with their real timestamp (ISO-8601) so they land on the correct day for analytics — otherwise they’d be stamped with the receive time:

{ "player_id": "77120", "event_type": "CashIn", "event_id": "hist-4412",
  "timestamp": "2025-12-01T18:22:00Z", "payload": { "amt": 5000 } }

Handling the response

{ "received": 3, "stored": 2, "deduped": 1, "status": "queued",
  "unknown_event_types": ["RouletteSpin"], "timestamp": "…" }
  • deduped > 0 — some events were retries (expected; not an error).
  • unknown_event_types present — extend your mapping for those types.
  • rejected > 0 — your policy is reject and those types aren’t mapped.
  • 4xx with code: BAD_REQUEST — fix the payload (see details); do not blind-retry a validation failure. 5xx — safe to retry.