Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Notifications

This page describes how to send players in-game (inbox) and push notifications: provider, template and rule setup in the panel; the game’s device registration, inbox reading and direct sending from the server. Read it if you want to use notification infrastructure without writing it yourself.

What it does

The notifications module (notifications) first writes a notification to a queue, then a background worker sends it to the provider (FCM, APNs or OneSignal). The API has no synchronous push send: when your request is written to the queue you get 200; delivery happens afterwards.

The notification text comes from a template. A template is written in the panel; the game sends only the template key and the fill data. You can produce a notification in two ways:

  • Sending from the server: Your game server calls POST /v1/notifications.
  • Rule: A rule you set up in the panel produces a notification automatically when a game event happens (for example a level up, quest completion or store purchase).

If the module is disabled, the endpoints return 404 with {"code":"module_disabled","module":"notifications"}. Enable the module on the Game settings › Modules page.

Where it lives in the panel

What you want to doPanel page
Enter push provider (FCM, APNs, OneSignal) credentialsNotifications › Providers
Write a template (title and body per language, placeholders)Notifications › Templates
Set up an event-driven rule, preview the rule liveNotifications › Rules
Send a test notification to a player, see the send history and devicesNotifications › History, Devices

Provider credentials are entered in the panel only by an authorized role and are stored sealed on the server; the panel shows only the fingerprint.

Note

Panel help pages are not translated yet; the panel links above point to the Turkish source, and the panel tabs are shown in Turkish.

Data model (in plain words)

  • Provider: fcm, apns, one_signal. Each is switched on and off separately per environment.
  • Device: A player’s registered token (token) at a provider and its platform (ios, android, web). The same (environment, provider, token) can be registered again; the response says which branch handled it (created, revived, refreshed).
  • Template: A key, a title and body per language, {{placeholder}} fields. If the requested language is missing, English (en) is used. Each notification is bound to the template version at the moment it was sent.
  • Channel: in_game (inbox only), push (push only), both.
  • Notification (inbox row): id, template_key, title, body, data, channel, status, created_at, sent_at, read_at. Status: queued, sent, delivered, failed, read.
  • Dedup key (dedup_key): The same (environment, player, key) is not written a second time.

Calls your game makes

There are two credential types: a player session (cookie or session token + x-crx-env: <environment uuid>) and a server key (Authorization: Bearer crx_live_<prefix>_<secret>).

Method and pathCredentialWhat it does
POST /v1/notificationsserver keyWrites a notification for a player to the queue
GET /v1/notificationsplayer sessionInbox (status, cursor, limit)
POST /v1/notifications/{id}/readplayer sessionMarks as read
POST /v1/devicesplayer sessionRegisters a push device
DELETE /v1/devices/{token}?provider=player sessionRevokes a device registration (204)
GET /v1/push/configno credential (x-crx-env required)Settings exposed to the client for web push
GET /v1/wsplayer sessionLive notification push socket (server to player only; the environment is given with the x-crx-env header, so it cannot be connected with the browser WebSocket API; frames arrive at most once, the durable surface is the inbox)

On the player endpoints the x-csrf-token and Origin checks are not applied; the session cookie or session token is enough.

The player side is ready in the TypeScript SDK: under player.notifications there are getPushConfig, registerDevice, unregisterDevice, enableWebPush, disableWebPush, inbox and markRead. There is no typed client for sending from the server; use raw REST. See TypeScript SDK.

Sending a notification from the server

POST /v1/notifications
Authorization: Bearer crx_live_<prefix>_<secret>
Content-Type: application/json

{
  "player_id": "0192f0f2-5b1a-7c3e-9d11-4a5b6c7d8e9f",
  "template_key": "daily_reward_ready",
  "locale": "en",
  "data": { "amount": 250 },
  "channel": "both",
  "dedup_key": "daily-2026-11-03"
}
{ "id": "0192f400-…", "duplicate": false }
  • locale: a short language code (tr, en …). If the requested language is not in the template, en is used.
  • dedup_key (optional, 1–256 characters): if the same key arrives a second time, no new row is written; 200 and duplicate: true are returned with the ID of the first notification.
  • 403: a client key or a player session. 404: the template does not exist in any language. 400: template_key is empty or longer than 128 characters, the locale format is invalid, or a placeholder in the template is missing from data.

Reading the inbox

GET /v1/notifications?status=queued&limit=20
x-crx-env: 0192f000-0000-7000-8000-00000000e001
{
  "items": [
    { "id": "0192f400-…", "template_key": "daily_reward_ready", "template_version": 2, "title": "Your daily reward is ready", "body": "250 gold is waiting for you.", "data": { "amount": 250 }, "channel": "both", "status": "sent", "created_at": "2026-11-03T09:00:00Z", "sent_at": "2026-11-03T09:00:02Z", "read_at": null }
  ],
  "next_cursor": null
}

The list is ordered newest to oldest (created_at, id). limit is 1–100; when next_cursor is null, it is the last page. Rows whose delivery failed are listed too. POST /v1/notifications/{id}/read makes the row read; a second call returns the same row and produces no new event.

Registering a push device

POST /v1/devices
x-crx-env: 0192f000-0000-7000-8000-00000000e001
Content-Type: application/json

{ "provider": "fcm", "platform": "android", "token": "<device token>", "device_meta": { "model": "Pixel 8" } }
{ "id": "…", "registration": "created", "provider": "fcm", "platform": "android", "created_at": "2026-11-03T09:00:00Z" }

provider is apns, fcm or one_signal; platform is ios, android or web. The token cannot be empty. The player ID comes from the session, not from the body. app_key separates the app in multi-app environments.

Note

The generated OpenAPI document writes the device provider as onesignal; the server and the SDK use one_signal. This is a known documentation deviation; send one_signal.

For web push, player.notifications.enableWebPush runs the browser permission flow with the settings it gets from GET /v1/push/config; on sign-out, session.logout() automatically revokes the web device registered from this page.

Rules: automatic notifications when an event happens

In the panel, a rule consists of a trigger (a game event from a closed list; for example level_up, quest_completed, store_purchase_completed, vip_tier_up), an optional condition, a template, a language, a channel and the recipient player path. Rule versions appear on the panel’s history tab. A notification itself does not trigger a new rule. The rule editor offers a “live preview” and a test send. For the detailed fields, see the panel page.

Versioning and replay safety

  • Sending: replay safety with dedup_key; there is no Idempotency-Key header.
  • Device registration and the read mark are naturally idempotent.
  • The template version is bound to the notification; changing the template later does not affect an old notification.

Events

When a notification is written to the queue, a notification_queued event is produced and the send worker consumes it. Among the types you can select as a webhook filter there is no notification type; endpoints registered without a filter receive this internal event too, but the envelope format is not a documented contract.

Limits

  • dedup_key is 1–256 characters; inbox limit is 1–100.
  • Push delivery is best-effort: if the provider rejects it, the row becomes failed. If a device is permanently invalid (for example the app was uninstalled), the device is revoked automatically.
  • If provider credentials are not defined, the notification waits in the queue and the provider status in the panel shows a warning.

Common errors

SymptomCauseFix
403 (send)A client key instead of a server keyUse the server key
404 (send)template_key does not exist in any languageCreate and publish the template in the panel
duplicate: trueThe same dedup_key was already writtenExpected behavior; use a new key for a new notification
The notification stays queuedThe provider is disabled or the credential is wrongCheck the provider status in the panel
400 (device)Empty token or unknown provider/platformFix the input

Working example

Draining the inbox on the player side:

/** Oyuncu tarafı: gelen kutusunu okur ve okunmamış bildirimleri okundu işaretler. */
import type { PlayerClient } from "@ferdiunal/open-play-ts";

/** İlk sayfadaki okunmamış bildirimleri yazdırır ve okundu yapar. */
export async function drainInbox(player: PlayerClient): Promise<number> {
  const page = await player.notifications.inbox({ limit: 20 });
  let marked = 0;
  for (const item of page.items) {
    if (item.readAt !== null) continue;
    console.log(item.title, item.body, item.data);
    await player.notifications.markRead(item.id);
    marked += 1;
  }
  return marked;
}

The curl example for sending from the server is above.