PAM IntegrationNative ingestion (/ingest)

Native ingestion (/ingest)

Some PAM / tracking systems can only POST their own payload to a URL — they can’t reshape the body to our canonical { player_id, event_type, … } envelope, and they expect a specific response. POST /api/v1/ingest is for exactly that: the PAM sends its native shape verbatim, and a per-brand envelope mapping tells us how to read it.

If you control the payload, prefer /events (our canonical shape). Use /ingest when the sender’s format is fixed.

Endpoint

POST https://crm-api.logiqdesk.com/api/v1/ingest?api_key=plq_…
Content-Type: application/json

Auth is the operator API key, sent as ?api_key= (or Authorization: Bearer, or x-api-key) — see Authentication. A brand-pinned key is the brand boundary. Accepts a single object, an array, or { "events": [ … ] } (max 100 per request).

Response contract

Unlike /events, this endpoint answers in a compact code form (so a PAM that checks a status code / code field is satisfied):

OutcomeHTTPBody
Accepted200{ "code": 0 }
Bad data / not configured200{ "code": 1, "error": "…" }
Rate limited429back off and retry (Retry-After)
Our failure5xx{ "code": 1, "error": "…" } — safe to retry

A 200 { "code": 1 } is a data problem (won’t succeed on retry); a 5xx is transient (retry). Always attach an idempotency key (mapped from your transaction_id, etc.) so retries never double-count.

The envelope mapping

/ingest requires the brand’s mapping to include an envelope section — how to read the top-level fields out of the PAM’s shape — on top of the normal mapping engine (event-type map, field maps, unmapped policy). The envelope is applied first, before canonical processing:

"envelope": {
  "playerId": "user_id",                       // → player_id (coerced to string)
  "eventType": "type",                         // → event_type
  "timestamp": ["timestamp", "registration_date"], // first present wins → ISO
  "eventId": "transaction_id"                  // → idempotency key
}

Refining the event type by a field

A PAM often sends one type with a discriminator field. Refine the canonical type conditionally (applied after eventTypeMap, in order):

"eventTypeRefine": [
  { "whenType": "bet", "field": "product", "equals": "sport", "set": "bet_sport" },
  { "whenType": "bet", "field": "source",  "equals": "bonus", "set": "bet_bonus" }
]

If the operator’s registration flow already captures marketing consent (so every player is legally consented), set:

"ingest": { "assumeConsent": true }

Players are then marked consented on their first registration event only — never on replay/update, so a later manual opt-out is never silently re-enabled.

Worked example (native type/user_id shape)

A PAM posts a deposit in its own shape:

{ "type": "deposit", "skin_id": 69, "user_id": 7177793,
  "amount": 50.00, "currency": "USD",
  "transaction_id": "TXN123456", "timestamp": "2025-02-19T14:30:00Z" }

With the envelope above + "eventTypeMap": { "deposit": "deposit" } and a payload field map (amountamount, currencycurrency), ingestion produces a canonical deposit for player 7177793, deduped on TXN123456, and replies { "code": 0 }. The worker then folds it into the player’s deposit totals. The raw body is always retained under _raw.

Static profile fields (name, DOB, language) only arrive in the event that carries them (usually registration). Players already ingested before those fields are added won’t back-fill automatically — ask the PAM for a one-time replay of registration (or a profile export) for existing users. Our ingestion upserts by the player id, so re-sending is safe (no duplicates).