Mapping engine
The mapping engine translates the PAM’s raw events into PlaylogiQ’s canonical model — configured per brand, in the dashboard, with zero hardcoding. The PAM sends whatever shape it already has; the operator maps it.
Where: Settings → Integrations → PAM mapping. Requires the platform-owner (Tier 2) permission. Each brand has its own mapping.
Why it exists
Every PAM names things differently — CashIn vs deposit, amt in cents vs
amount in units, profile.mail vs a top-level email. Instead of asking either
side to change, the operator declares the translation once per brand. A live
tester shows exactly what ingestion will produce before you rely on it.
The raw payload is always retained (under _raw), so a mapping never loses
information — you can refine it later and re-derive.
The four parts of a mapping
A mapping is a JSON config with four independent parts.
1. Event type map
Map each raw PAM event name to a canonical event type.
"eventTypeMap": {
"CashIn": "deposit",
"CashOut": "withdrawal_request",
"FreeSpinGrant":"bonus_granted",
"KycApproved": "kyc_verified"
}If a raw type isn’t listed, PlaylogiQ falls back to built-in alias
normalization (e.g. player.deposit → deposit); anything still unresolved is
handled by the unmapped policy.
2. Payload field map
Extract fields from the raw payload into canonical payload fields, per
canonical event (or * for all events). Each rule is from → to with an
optional transform.
"payloadFieldMap": {
"deposit": [
{ "from": "amt", "to": "amount", "transform": "cents_to_units" },
{ "from": "cur", "to": "currency", "transform": "currency" }
],
"*": [
{ "from": "ext.ip", "to": "ip_address", "transform": "trim" }
]
}from is a dotted path into the raw payload (ext.ip reads
payload.ext.ip). The derived fields are overlaid on the stored payload; the
original stays under _raw.
3. Player field map
Write payload values onto the player — either a first-class column or a custom field.
"playerFieldMap": [
{ "from": "profile.mail", "to": "column:email", "transform": "trim|lower" },
{ "from": "profile.fname", "to": "column:first_name" },
{ "from": "vipCode", "to": "custom_field:vip_code" }
]column:<name>writes an allow-listed player column. Allowed columns:email,phone,first_name,last_name,gender,timezone,registration_date,marketing_consent.custom_field:<key>writesplayers.metadata.custom.<key>(existing metadata is merged, never clobbered).
Anything outside the allow-list is ignored — a mapping can never touch
tenant_id, brand_id, status, or derived financial metrics.
4. Unmapped policy
What to do with an event whose type resolves to neither canonical nor mapped:
| Policy | Behaviour |
|---|---|
store_raw_flag | Default. Keep the raw event and flag it as unknown (surfaced in the API response’s unknown_event_types). |
reject | Refuse the event; reported back in the response’s rejected / rejected_event_types. |
ignore | Silently drop the event. |
Transform vocabulary
Transforms are a fixed set — no operator-supplied code ever runs. Chain them
with | (applied left to right).
| Transform | Effect |
|---|---|
number | Parse to a number (invalid → dropped). |
cents_to_units | Divide by 100 (minor units → major). |
upper | Uppercase string. |
lower | Lowercase string. |
trim | Trim whitespace. |
date_iso | Parse to an ISO-8601 timestamp (invalid → dropped). |
currency | Uppercase, first 3 chars (e.g. eur → EUR). |
bool | Truthy strings (true/1/yes/y/on/t) → true. |
enum:{A:B,...} | Map exact values (unmatched values pass through). |
Example chain: trim|lower normalises " A@B.COM " → "a@b.com".
Worked example
Raw event the PAM sends:
{
"event_type": "CashIn",
"payload": {
"amt": 2500,
"cur": "eur",
"profile": { "mail": " Alice@Example.COM " },
"vipCode": "gold"
}
}Mapping:
{
"eventTypeMap": { "CashIn": "deposit" },
"payloadFieldMap": {
"deposit": [
{ "from": "amt", "to": "amount", "transform": "cents_to_units" },
{ "from": "cur", "to": "currency", "transform": "currency" }
]
},
"playerFieldMap": [
{ "from": "profile.mail", "to": "column:email", "transform": "trim|lower" },
{ "from": "vipCode", "to": "custom_field:vip_code" }
],
"unmappedPolicy": "store_raw_flag"
}What ingestion produces:
- Event type →
deposit - Derived payload →
{ "amount": 25, "currency": "EUR" }(overlaid on the raw payload; raw kept under_raw) - Player →
email = "alice@example.com",metadata.custom.vip_code = "gold"
The live tester
On the PAM mapping page, paste a raw event into the tester and run it. It executes the draft config (what you’re editing, unsaved) through the exact same engine the ingestion uses, and shows the canonical type, disposition (kept / ignored / rejected), derived payload, and player attributes. Iterate until it’s right, then Save — no back-and-forth with either team.
Versioning & isolation
- One active mapping per brand; saving bumps the version.
- The mapping is stored per brand and gated by Row-Level Security — it is never visible or applicable across the brand boundary.
- No mapping configured? Ingestion falls back to built-in canonical-name normalization — existing integrations keep working unchanged.