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"| Operation | Endpoint |
|---|---|
| List endpoints | GET /api/v1/webhooks |
| Create endpoint | POST /api/v1/webhooks → returns secret once |
| Get / update / delete | GET | PATCH | DELETE /api/v1/webhooks/{id} |
| Rotate the secret | PATCH /api/v1/webhooks/{id} with {"rotate_secret": true} |
| Delivery log | GET /api/v1/webhooks/{id}/deliveries |
| Send a signed test event | POST /api/v1/webhooks/{id}/ping |
Endpoint URLs must be public https hosts — private/loopback/link-local targets are rejected (SSRF guard). Omit
brandfor 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
2xxresponse 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:
| Event | Fires when | data |
|---|---|---|
player.created | A player is created | { player_id, external_id } |
player.updated | A player record is updated | player fields |
player.status_changed | A player’s status changes | { player_id, status, … } |
player.converted | A tracked conversion is attributed to a player | { player_id, … } |
segment.recomputed | A segment’s membership is recomputed | { segment_id, player_count } |
campaign.created | A campaign is created | { campaign_id, … } |
message.delivered | A message was delivered (email / telegram / push / in-app) | { player_id, channel, campaign_send_id? } |
message.opened | An email open is first recorded (tracking pixel) | { campaign_id, player_id, channel } |
message.clicked | A tracked link is first clicked | { campaign_id, player_id, channel } |
message.bounced | An email hard-bounces (permanent failure) | { player_id, channel, reason, campaign_send_id? } |
email.unsubscribed | A recipient uses the one-click unsubscribe | { player_id, email, channel } |
bonus.granted | A bonus is granted to a player | bonus + player refs |
The
message.*+email.unsubscribedevents are the delivery feedback a marketing-automation integration needs (delivered / opened / clicked / bounced / unsubscribed).message.openedandmessage.clickedfire 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.