PAM IntegrationMapping engine

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.depositdeposit); 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 fromto 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> writes players.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:

PolicyBehaviour
store_raw_flagDefault. Keep the raw event and flag it as unknown (surfaced in the API response’s unknown_event_types).
rejectRefuse the event; reported back in the response’s rejected / rejected_event_types.
ignoreSilently drop the event.

Transform vocabulary

Transforms are a fixed set — no operator-supplied code ever runs. Chain them with | (applied left to right).

TransformEffect
numberParse to a number (invalid → dropped).
cents_to_unitsDivide by 100 (minor units → major).
upperUppercase string.
lowerLowercase string.
trimTrim whitespace.
date_isoParse to an ISO-8601 timestamp (invalid → dropped).
currencyUppercase, first 3 chars (e.g. eur EUR).
boolTruthy 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 typedeposit
  • Derived payload{ "amount": 25, "currency": "EUR" } (overlaid on the raw payload; raw kept under _raw)
  • Playeremail = "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.