PAM IntegrationOutbound webhooks

Outbound webhooks

Webhooks are the outbound direction — PlaylogiQ calling your systems when something happens. They are optional and independent of the inbound event ingestion.

A PAM integration typically needs only inbound ingestion. Webhooks are for when you want PlaylogiQ to notify an external system (data warehouse, ops tooling, a BI pipeline) of platform-side events.

Configuring an endpoint

You can register endpoints two ways — both store the same thing:

Dashboard — Settings → Webhooks → add an endpoint URL and the events to subscribe to. A signing secret is generated for you and shown once.

REST API — manage endpoints programmatically (requires the write scope):

# Create an endpoint — the `secret` is returned ONCE, store it.
curl -X POST https://crm-api.logiqdesk.com/api/v1/webhooks \
  -H "x-api-key: plq_xxx" -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/playlogiq",
    "events": ["message.delivered","message.opened","message.clicked","email.unsubscribed"]
  }'
 
# List / inspect / rotate / delete
curl  https://crm-api.logiqdesk.com/api/v1/webhooks            -H "x-api-key: plq_xxx"
curl  https://crm-api.logiqdesk.com/api/v1/webhooks/{id}/deliveries -H "x-api-key: plq_xxx"
curl -X POST  https://crm-api.logiqdesk.com/api/v1/webhooks/{id}/ping  -H "x-api-key: plq_xxx"
curl -X PATCH https://crm-api.logiqdesk.com/api/v1/webhooks/{id} \
  -H "x-api-key: plq_xxx" -H "Content-Type: application/json" -d '{"rotate_secret": true}'
curl -X DELETE https://crm-api.logiqdesk.com/api/v1/webhooks/{id} -H "x-api-key: plq_xxx"
OperationEndpoint
List endpointsGET /api/v1/webhooks
Create endpointPOST /api/v1/webhooks → returns secret once
Get / update / deleteGET | PATCH | DELETE /api/v1/webhooks/{id}
Rotate the secretPATCH /api/v1/webhooks/{id} with {"rotate_secret": true}
Delivery logGET /api/v1/webhooks/{id}/deliveries
Send a signed test eventPOST /api/v1/webhooks/{id}/ping

Endpoint URLs must be public https hosts — private/loopback/link-local targets are rejected (SSRF guard). Omit brand for a tenant-wide endpoint, or pass a brand id/slug to scope it to one brand.

Delivery semantics

  • PlaylogiQ POSTs a JSON body to your endpoint.
  • Delivery is at-least-once with a persistent queue and exponential backoff on failure: retries at roughly 1m → 5m → 15m → 1h, up to 5 attempts, after which the delivery is marked failed.
  • Every attempt is recorded in a delivery log you can inspect in the dashboard (status, response code, timing).
  • A 2xx response marks the delivery successful; anything else (or a timeout, default 10s) reschedules it.

Verifying the signature

Each request carries an HMAC-SHA256 signature of the raw body, keyed by your endpoint’s secret:

X-PlaylogiQ-Signature: sha256=<hex>

Recompute and compare (constant-time) to authenticate the call:

import { createHmac, timingSafeEqual } from "node:crypto"
 
function verify(rawBody, header, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex")
  const a = Buffer.from(header)
  const b = Buffer.from(expected)
  return a.length === b.length && timingSafeEqual(a, b)
}

Always verify against the raw request body (before JSON parsing) — any re-serialization will change the bytes and break the signature.

Payload shape

{
  "event": "message.opened",
  "created_at": "2026-10-12T09:30:00.000Z",
  "data": { "…": "event-specific data" }
}

The event field identifies the subscribed event, created_at is the delivery timestamp, and data carries the event-specific fields. Sign/verify over the raw body (see above).

Event catalog

The events an endpoint can subscribe to today:

EventFires whendata
player.createdA player is created{ player_id, external_id }
player.updatedA player record is updatedplayer fields
player.status_changedA player’s status changes{ player_id, status, … }
player.convertedA tracked conversion is attributed to a player{ player_id, … }
segment.recomputedA segment’s membership is recomputed{ segment_id, player_count }
campaign.createdA campaign is created{ campaign_id, … }
message.deliveredA message was delivered (email / telegram / push / in-app){ player_id, channel, campaign_send_id? }
message.openedAn email open is first recorded (tracking pixel){ campaign_id, player_id, channel }
message.clickedA tracked link is first clicked{ campaign_id, player_id, channel }
message.bouncedAn email hard-bounces (permanent failure){ player_id, channel, reason, campaign_send_id? }
email.unsubscribedA recipient uses the one-click unsubscribe{ player_id, email, channel }
bonus.grantedA bonus is granted to a playerbonus + player refs

The message.* + email.unsubscribed events are the delivery feedback a marketing-automation integration needs (delivered / opened / clicked / bounced / unsubscribed). message.opened and message.clicked fire once per send (on the first open / click). The catalog grows additively; subscribe only to the events you need. (These are platform events, distinct from the canonical player events the PAM sends in.)

No transactional callbacks to the PAM

By design, PlaylogiQ does not make synchronous, transactional calls back to a PAM (e.g. to grant a bonus in the wallet). The platform consumes the PAM’s event stream and drives CRM/gamification off it; it does not act as a controller of the PAM’s ledger. Webhooks are notifications, not commands.