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’sunknown_event_types, so you notice and can add a mapping. Nothing is lost.reject— the event is refused and reported inrejected_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 } // safeBackfill / 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_typespresent — extend your mapping for those types.rejected > 0— your policy isrejectand those types aren’t mapped.4xxwithcode: BAD_REQUEST— fix the payload (seedetails); do not blind-retry a validation failure.5xx— safe to retry.