Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/01-getting-started/04-using-the-enjin-platform.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ There are two ways to receive the transaction status and information:

You can also receive real-time, push-based notifications over WebSocket instead of polling — see [WebSocket Events](/03-api-reference/03-websocket-events.md).

For on-chain notifications such as transfers, [Event Inbox](/05-enjin-platform/03-working-with-events.md#event-inbox) lets you poll when convenient or recover notifications missed during a WebSocket outage. Process and acknowledge pending deliveries within the 14-day retention window.

### Receive Transaction Information Using the Platform User Interface

To check the transaction status, head over to the [Transactions](https://platform.enjin.io/transactions) page. Each row shows the transaction's `UUID`, `Action` (e.g. `MultiTokens.create_collection`), `State`, and `Extrinsic Hash` once it has been broadcast.
Expand Down
128 changes: 128 additions & 0 deletions docs/03-api-reference/01-queries/09-event-inbox-queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
title: "Event Inbox"
slug: "event-inbox"
description: "Retrieve pending Enjin Platform notifications with GetPendingEvents for polling or recovery after a WebSocket disconnect."
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

:::tip GraphQL Endpoint
`https://platform.enjin.io/graphql`
:::

Event Inbox stores eligible on-chain notifications for your account so you can poll at a convenient time or recover notifications missed over WebSocket. Authenticate with your Enjin Platform API token. Notifications are captured automatically; no Pusher connection or opt-in is required.

See [Working with Events](/05-enjin-platform/03-working-with-events.md#event-inbox) for the processing workflow and [On-chain notifications](/03-api-reference/03-websocket-events.md#on-chain-notifications) for the supported events.

## GetPendingEvents

Returns the oldest unacknowledged notifications for the authenticated user on the requested network and chain, up to `limit`.

```graphql
GetPendingEvents(
network: Network! = ENJIN
chain: Chain! = MATRIX
limit: Int! = 100
): [UnprocessedEvent!]!
```

| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `network` | `Network!` | `ENJIN` | Network to retrieve notifications from: `ENJIN` or `CANARY`. |
| `chain` | `Chain!` | `MATRIX` | Chain to retrieve notifications from: `MATRIX` or `RELAY`. |
| `limit` | `Int!` | `100` | Maximum number of notifications to return, from `1` to `100`. |

All three arguments can be omitted to use their defaults. Poll each network and chain you need separately.

### Response fields

The result is a list of `UnprocessedEvent` objects, or `[]` if there are no pending notifications.

| Field | Type | Description |
| --- | --- | --- |
| `uuid` | `ID!` | Delivery UUID used for deduplication and acknowledgment. The same UUID is included in the corresponding Pusher notification. |
| `event` | `String!` | Exact Pusher event name, for example `BalancesTransfer`. |
| `payload` | `JSON!` | Notification payload as a JSON object. The delivery UUID is returned separately in `uuid`; Pusher adds it to the payload as `uuid`. |

### Example

This example retrieves notifications on Enjin Relaychain.

<Tabs>
<TabItem value="graphql" label="GraphQL">

```graphql
query GetPendingEvents(
$network: Network! = ENJIN
$chain: Chain! = MATRIX
$limit: Int! = 100
) {
GetPendingEvents(network: $network, chain: $chain, limit: $limit) {
uuid
event
payload
}
}
```

**Variables:**

```json
{
"network": "ENJIN",
"chain": "RELAY",
"limit": 100
}
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl --location 'https://platform.enjin.io/graphql' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <YOUR_API_TOKEN>' \
-d '{"query":"query GetPendingEvents($network: Network! = ENJIN, $chain: Chain! = MATRIX, $limit: Int! = 100) { GetPendingEvents(network: $network, chain: $chain, limit: $limit) { uuid event payload } }","variables":{"network":"ENJIN","chain":"RELAY","limit":100}}'
```

</TabItem>
<TabItem value="response" label="Response">

```json
{
"data": {
"GetPendingEvents": [
{
"uuid": "af078b53-701a-40a9-b0e6-2dc39f3fc47d",
"event": "BalancesTransfer",
"payload": {
"chain": "RELAY",
"network": "ENJIN",
"publicKey": "0xa7208d10c6622f3f7eca1551de8355fde9de577dbb308d38994ace561738a51f"
}
},
{
"uuid": "7a862834-6f50-45bc-8bf0-c8231d00ec78",
"event": "BalancesTransfer",
"payload": {
"chain": "RELAY",
"network": "ENJIN",
"publicKey": "0xa7208d10c6622f3f7eca1551de8355fde9de577dbb308d38994ace561738a51f"
}
}
]
}
}
```

</TabItem>
</Tabs>

These two notifications have identical payloads but different UUIDs. Treat them as separate deliveries; deduplicate by `uuid`, not by payload or event name.

### Retrieving the next batch

Reading notifications does not acknowledge them. There are no page, offset, or cursor arguments: repeated queries return the oldest pending notifications until they are acknowledged or expire. After successfully processing a batch, call [`AcknowledgeEvents`](/03-api-reference/02-mutations/09-event-inbox-mutations.md#acknowledgeevents) with its UUIDs, then query again for the next batch.

Notifications are retained for **14 days** from dispatch. Unacknowledged notifications expire and are automatically purged after that period. Event Inbox does not backfill historical events.
88 changes: 88 additions & 0 deletions docs/03-api-reference/02-mutations/09-event-inbox-mutations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: "Event Inbox"
slug: "event-inbox"
description: "Acknowledge processed Enjin Platform notifications with AcknowledgeEvents."
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

:::tip GraphQL Endpoint
`https://platform.enjin.io/graphql`
:::

Use Event Inbox acknowledgments after successfully processing notifications received through [`GetPendingEvents`](/03-api-reference/01-queries/09-event-inbox-queries.md#getpendingevents) or the corresponding Pusher subscription. Authenticate with your Enjin Platform API token.

## AcknowledgeEvents

Marks the authenticated user's pending notifications as acknowledged, removing them from subsequent `GetPendingEvents` results.

```graphql
AcknowledgeEvents(uuids: [ID!]!): Int!
```

| Argument | Type | Description |
| --- | --- | --- |
| `uuids` | `[ID!]!` | Between `1` and `100` distinct, valid delivery UUIDs. Use the top-level `uuid` from an inbox entry or the `uuid` in the corresponding Pusher notification's payload. |

This mutation is **network- and chain-agnostic**. It accepts UUIDs from multiple networks and chains in one request and takes no `network` or `chain` argument.

### Example

<Tabs>
<TabItem value="graphql" label="GraphQL">

```graphql
mutation AcknowledgeEvents($uuids: [ID!]!) {
AcknowledgeEvents(uuids: $uuids)
}
```

**Variables:**

```json
{
"uuids": [
"af078b53-701a-40a9-b0e6-2dc39f3fc47d",
"7a862834-6f50-45bc-8bf0-c8231d00ec78"
]
}
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl --location 'https://platform.enjin.io/graphql' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <YOUR_API_TOKEN>' \
-d '{"query":"mutation AcknowledgeEvents($uuids: [ID!]!) { AcknowledgeEvents(uuids: $uuids) }","variables":{"uuids":["af078b53-701a-40a9-b0e6-2dc39f3fc47d","7a862834-6f50-45bc-8bf0-c8231d00ec78"]}}'
```

</TabItem>
<TabItem value="response" label="Response">

```json
{
"data": {
"AcknowledgeEvents": 2
}
}
```

</TabItem>
</Tabs>

### Return value and retries

The integer result counts only notifications that changed from unacknowledged to acknowledged in this request. Repeating the example after both notifications have been acknowledged returns `0`.

Unknown, expired, already acknowledged UUIDs, and UUIDs belonging to another user do not contribute to the count. A count smaller than the number submitted is therefore possible. Invalid UUIDs, duplicate UUIDs within one request, an empty list, or more than 100 UUIDs fail validation.

Acknowledgment is safe to retry if a request fails or its response is lost. Deduplicate UUIDs within each batch, and send only those whose processing has completed successfully. If only part of a batch succeeds, acknowledge that subset and leave failed notifications pending for retry.

:::tip Acknowledge to advance
`GetPendingEvents` has no pagination. Acknowledge processed notifications so the next query can return later pending notifications. Receiving a WebSocket message or reading an inbox entry does not acknowledge it automatically.
:::

See [Working with Events](/05-enjin-platform/03-working-with-events.md#event-inbox) for polling, reconnect recovery, and deduplication guidance.
65 changes: 58 additions & 7 deletions docs/03-api-reference/03-websocket-events.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
---
title: "WebSocket Events"
slug: "websocket-events"
description: "Subscribe to real-time Enjin Platform events over WebSocket using the Pusher protocol."
description: "Subscribe to Enjin Platform notifications over Pusher, and use Event Inbox to recover missed on-chain notifications."
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

After you submit a transaction, there are two ways to find out what happened to it. You can keep asking the API ("is it finalized yet?") — that's **polling**, and it works, but it wastes requests and adds delay. Or the platform can tell *you* the moment something happens — a transaction changing state, a managed wallet being created, a user linking their wallet. That's what this page covers.
The Enjin Platform can notify your application when something changes — an on-chain transfer, a transaction changing state, a managed wallet being created, or a user linking their wallet. This page covers receiving those notifications in real time.

These notifications are delivered over a **WebSocket** — a connection your application opens once and keeps open, so the server can send it messages at any time. The Enjin Platform sends them through [Pusher](https://pusher.com/), a widely-used message-delivery service with a [documented protocol](https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/) and official client libraries for most languages.

For eligible [on-chain notifications](#on-chain-notifications), **Event Inbox** provides a second delivery option: poll [`GetPendingEvents`](/03-api-reference/01-queries/09-event-inbox-queries.md#getpendingevents) at a time that suits your application, or use it alongside WebSocket to recover missed notifications. The event names and notification data match across both transports, and a shared delivery `uuid` lets you deduplicate them. See [Working with Events](/05-enjin-platform/03-working-with-events.md#event-inbox) for the complete workflow.

:::info You don't need a Pusher account
Pusher is just the delivery mechanism. You connect using Enjin's public app key below and authenticate with your regular Enjin Platform API token — there's nothing to sign up for or configure on Pusher's side.
:::
Expand All @@ -25,6 +27,8 @@ Your account's events are broadcast on a **channel** — a named stream you subs

The `auth` signature is tied to the `socket_id` it was issued for, and every new connection gets a new `socket_id` — so if the connection drops and you reconnect, you repeat steps 2 and 3.

Reconnecting restores live delivery. To recover on-chain notifications missed during the outage, drain Event Inbox after the subscription succeeds and acknowledge each successfully processed delivery. Notifications remain available for 14 days from dispatch; receiving them over WebSocket does not automatically acknowledge them.

If that sounds like a lot of bookkeeping: it mostly isn't yours to do. A first-party Pusher library (like [pusher-js](https://github.com/pusher/pusher-js)) handles the connection, the re-authentication on reconnect, and the keepalive for you — you just tell it how to call the mutation. That's the recommended setup, and the quick start below is exactly that. Only if no Pusher library exists for your stack do you need to speak the protocol yourself — see [Subscribing over a raw WebSocket](#subscribing-over-a-raw-websocket).

## Quick start
Expand Down Expand Up @@ -102,7 +106,7 @@ ManagedWalletCreated {
}
```

The full list of events you can receive is in the [Platform events](#platform-events) reference below.
See [On-chain notifications](#on-chain-notifications) for events also available through Event Inbox, and [Platform events](#platform-events) for lifecycle notifications.

:::warning Keep your API token server-side
`AuthenticatePusherSocket` requires your platform API token, so the socket should be opened from your backend (or the mutation proxied through it) — never ship the token to a browser or game client.
Expand Down Expand Up @@ -244,7 +248,7 @@ Response:

## Subscribing over a raw WebSocket {#subscribing-over-a-raw-websocket}

If a first-party Pusher library isn't available for your stack, you can speak the protocol directly — it's the same three steps from [How it works](#how-it-works), done by hand. Note that the `data` field of every Pusher protocol frame is a JSON-*encoded string*, so it needs a second parse.
If a first-party Pusher library isn't available for your stack, you can speak the protocol directly — it's the same three steps from [How it works](#how-it-works), done by hand. In the incoming messages shown below, `data` is a JSON-*encoded string*, so it needs a second parse.

1. Connect to `wss://ws-us2.pusher.com/app/8ab7ab8c519e8f59b635?protocol=7`. The server sends:

Expand All @@ -271,16 +275,63 @@ If a first-party Pusher library isn't available for your stack, you can speak th

```json
{
"event": "TransactionStateChanged",
"channel": "private-user.b1acd213-2b27-406e-a21b-02307b52eca0",
"data": "{\"uuid\":\"0af09287-30ff-43c9-a13d-75ad1ea714c3\",\"state\":\"FINALIZED\"}"
"event": "BalancesTransfer",
"data": "{\"network\":\"ENJIN\",\"chain\":\"RELAY\",\"uuid\":\"f2342ed4-3504-4f81-b4b5-315635283652\",\"publicKey\":\"0xa7208d10c6622f3f7eca1551de8355fde9de577dbb308d38994ace561738a51f\"}",
"channel": "private-user.b1acd213-2b27-406e-a21b-02307b52eca0"
}
```

Your client is also responsible for keepalive: if the connection is quiet for longer than the `activity_timeout` reported in `pusher:connection_established`, send `{"event":"pusher:ping","data":{}}` and expect a `pusher:pong` back (the server may likewise ping you). And remember that a reconnect is a new socket — repeat the authenticate-and-subscribe handshake every time.

## On-chain notifications {#on-chain-notifications}

These notifications are delivered through both Pusher and Event Inbox for eligible wallets and collections associated with your account. Each delivery includes a UUID that is shared across the two transports. Use it to deduplicate processing and pass it to [`AcknowledgeEvents`](/03-api-reference/02-mutations/09-event-inbox-mutations.md#acknowledgeevents) after processing succeeds.

All Pusher payloads in this group contain `network`, `chain`, `uuid`, and `publicKey`. Addresses are public keys (`0x...`). Additional fields depend on the event:

| Event | Change | Additional payload fields |
| --- | --- | --- |
| `BalancesTransfer` | ENJ is transferred | None |
| `MultiTokensTransferred` | Tokens are transferred | `id`: full token ID (`collectionId-tokenId`) |
| `MultiTokensMinted` | Tokens are minted | `id`: full token ID |
| `MultiTokensBurned` | Tokens are burned | `id`: full token ID |
| `MarketplaceListingCreated` | A marketplace listing is created | `tokenId`: full token ID; `listingId`: listing ID |
| `MarketplaceListingFilled` | A marketplace listing is filled | `tokenId`: full token ID; `listingId`: listing ID |
| `MarketplaceListingCancelled` | A marketplace listing is cancelled or removed under its minimum | `tokenId`: full token ID; `listingId`: listing ID |
| `NominationPoolsBond` | An account bonds into a nomination pool | `id`: pool ID |
| `NominationPoolsUnbond` | An account initiates unbonding from a nomination pool | `id`: pool ID |

For example, a Pusher client library delivers this decoded `BalancesTransfer` payload:

```json
{
"network": "ENJIN",
"chain": "RELAY",
"uuid": "f2342ed4-3504-4f81-b4b5-315635283652",
"publicKey": "0xa7208d10c6622f3f7eca1551de8355fde9de577dbb308d38994ace561738a51f"
}
```

The same notification returned by `GetPendingEvents(network: ENJIN, chain: RELAY)` has this shape:

```json
{
"uuid": "f2342ed4-3504-4f81-b4b5-315635283652",
"event": "BalancesTransfer",
"payload": {
"network": "ENJIN",
"chain": "RELAY",
"publicKey": "0xa7208d10c6622f3f7eca1551de8355fde9de577dbb308d38994ace561738a51f"
}
}
```

The inbox returns the delivery UUID separately from `payload`. Its `event` is the exact Pusher name, without the dots or spaces used for on-chain event logs. These notifications identify what changed; query the relevant account, token, listing, or pool for its current state.

## Platform events

The following lifecycle notifications are delivered over WebSocket and are separate from the on-chain notifications retained in Event Inbox. In `TransactionCreated` and `TransactionStateChanged`, `uuid` identifies the **transaction** for `GetTransaction`, rather than an inbox delivery to acknowledge.

Event payloads are deliberately lightweight: they carry just enough to tell you *what* changed, and you fetch whatever else you need with a follow-up GraphQL query. For example, when `TransactionStateChanged` reports `FINALIZED`, query [`GetTransaction(uuid:)`](/03-api-reference/01-queries/01-transactions-queries.md#gettransaction) to read the on-chain outcome and emitted events — see [Working with Events](/05-enjin-platform/03-working-with-events.md) for that flow.

| Event | Fires when |
Expand Down
Loading
Loading