Connect to the WebSocket¶
Photon pushes transaction status updates over a WebSocket connection. This page documents the wire protocol; for a walkthrough of setting one up, see Register a WebSocket.
Endpoint¶
The exact connection URL, along with your token, is returned by Register WebSocket credential.
Authentication¶
| Header | Value |
|---|---|
x-ws-token |
The WebSocket token issued by Register WebSocket credential. |
The token is validated before the connection upgrades. A missing or unknown token is rejected with HTTP 401 and error code AUTH_001 in the standard error envelope — no WebSocket connection is established.
Warning
Browsers cannot set custom headers on WebSocket connections, so connect from a server-side environment where you can send x-ws-token.
Message flow¶
- Greeting — immediately after the upgrade, the server sends exactly:
-
Notifications — as your transactions change status, the server sends bare notification payloads (see the schema below). There is no event or type wrapper around them.
-
Echo — any text message you send is echoed back as:
This doubles as an application-level heartbeat. WebSocket ping frames are answered with pong frames.
You receive notifications for transactions submitted by the account the token belongs to. There is no subscription protocol — connecting is subscribing.
Notification payload¶
Notifications are sent as a single JSON object per message:
{
"txId": "68fa3450539a3c9d28bbca33",
"chainId": 8453,
"status": "EXECUTED",
"costUSD": 0.84,
"totalNativeTokenUsed": "31500000000000",
"gasPrice": "1500000000",
"txHash": "0x3f8b2c…",
"submittedAt": "2026-07-29T09:14:01.120Z",
"updatedAt": "2026-07-29T09:14:03.512Z",
"retries": 0
}
| Field | Type | Presence | Description |
|---|---|---|---|
txId |
string | always | Transaction ID returned when you submitted the transaction. |
chainId |
number | always | Chain the transaction targets. |
status |
string | always | One of NOT_PICKED_UP, PENDING, EXECUTED, FAILED, NEEDS_TO_BE_RETRIED. |
costUSD |
number | when known | Sponsored gas cost in USD. |
totalNativeTokenUsed |
string | when known | Amount of native token spent, as an integer string. |
gasPrice |
string | when known | Gas price used, as an integer string. |
txHash |
string | when known | On-chain transaction hash. |
submittedAt |
string | always | ISO-8601 timestamp of submission. |
updatedAt |
string | always | ISO-8601 timestamp of this status change. |
retries |
number | always | Retry attempts made so far. |
Optional fields are omitted when not yet known — they are never sent as null. This payload is identical to the webhook request body sent to URLs registered via Register webhooks.
Example¶
Expected output on connect:
Followed by notifications as your transactions progress:
< {"txId":"68fa3450539a3c9d28bbca33","chainId":8453,"status":"PENDING","submittedAt":"2026-07-29T09:14:01.120Z","updatedAt":"2026-07-29T09:14:01.120Z","retries":0}
< {"txId":"68fa3450539a3c9d28bbca33","chainId":8453,"status":"EXECUTED","costUSD":0.84,"totalNativeTokenUsed":"31500000000000","gasPrice":"1500000000","txHash":"0x3f8b2c…","submittedAt":"2026-07-29T09:14:01.120Z","updatedAt":"2026-07-29T09:14:03.512Z","retries":0}
import WebSocket from "ws";
const ws = new WebSocket("wss://photon.example.com/ws", {
headers: { "x-ws-token": process.env.WS_TOKEN! },
});
ws.on("message", (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.message === "WebSocket connected!") return; // greeting
if (msg.echo !== undefined) return; // echo of a message you sent
// Anything else is a transaction notification
console.log(msg.txId, msg.status, msg.txHash ?? "(no hash yet)");
});
Delivery and reconnection¶
- Updates emitted while you are disconnected are not queued or replayed on reconnect.
- The per-connection delivery buffer is bounded; a consumer that reads too slowly can miss intermediate updates and will keep receiving from the live stream.
- After a disconnect, reconnect with exponential backoff and reconcile any transactions still in flight via Get transaction status — the record there is authoritative.
- Treat the greeting message as confirmation that authentication succeeded and the stream is live.