Gateway notifications

Connect to the gateway WebSocket, authenticate with JWT or API key, and receive wallet, teller, identity, and loan events.

This socket pushes notifications for a logged-in account (wallet, teller, identity, loans). It is not the Spot Trading API WebSocket (wss://ws.bit2me.com/v1/trading).

You can only receive events after a successful authenticate. If authentication fails, the server closes with code 4000.

URL

wss://ws.bit2me.com/v1

Do not use the Pro URL (wss://ws.bit2me.com/v1/trading) for these notifications.

Authenticate

After open, send one authenticate message. Pick JWT or API key, not both on the same send.

Option 1 — JWT (token)

  1. Sign POST https://gateway.bit2me.com/v1/signin/apikey with HMAC. See Authentication.
  2. Read accessToken.token from the 200 body. Honour accessToken.expirationTime from that response. Do not invent a TTL.
  3. For a subaccount, include userId in the sign-in body.
curl -X POST 'https://gateway.bit2me.com/v1/signin/apikey' \
  -H 'Content-type: application/json' \
  -H 'X-API-KEY: <api key>' \
  -H 'api-signature: <hmac>' \
  -H 'x-nonce: <nonce>'

Subaccount session:

curl -X POST 'https://gateway.bit2me.com/v1/signin/apikey' \
  -H 'Content-type: application/json' \
  -H 'X-API-KEY: <api key>' \
  -H 'api-signature: <hmac>' \
  -H 'x-nonce: <nonce>' \
  -d '{"userId":"<subaccount uuid>"}'

Then on the socket:

{ "type": "authenticate", "payload": { "token": "<accessToken.token>" } }

Option 2 — API key (apikey)

Send the public API key. The OpenAPI sample uses an array:

{ "type": "authenticate", "payload": { "apikey": ["<api-key>"] } }

This is not HMAC. HMAC is only for REST (and for fetching a JWT in option 1).

Connect example (browser)

Replace jwt or apikey.

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript">
      const jwt = "ACCESS_TOKEN"; // accessToken.token from POST /v1/signin/apikey
      const apikey = "API_KEY"; // public key, if you authenticate with apikey instead

      const ws = new WebSocket("wss://ws.bit2me.com/v1");

      ws.addEventListener("error", (err) => {
        console.error("websocket error:", err);
      });

      ws.addEventListener("open", () => {
        console.debug("websocket connected");

        // JWT
        ws.send(JSON.stringify({ type: "authenticate", payload: { token: jwt } }));

        // or API key (do not send both)
        // ws.send(JSON.stringify({ type: "authenticate", payload: { apikey: [apikey] } }));
      });

      ws.addEventListener("message", (message) => {
        const msg = JSON.parse(message.data);
        console.debug(`websocket message: type: ${msg.type}, id: ${msg.id}, time: ${msg.time}`);
      });

      ws.addEventListener("close", (evt) => {
        if (evt.code === 4000) {
          console.log(`Error while authenticating: ${evt.code}/${evt.reason}`);
        } else if (evt.code === 4001) {
          console.log(`Error rate limit: ${evt.code}/${evt.reason}`);
        }
        // reconnect with backoff
      });
    </script>
  </head>
  <body></body>
</html>

Connect example (Node.js)

const WebSocket = require("ws");

const jwt = process.env.BIT2ME_ACCESS_TOKEN; // accessToken.token

const ws = new WebSocket("wss://ws.bit2me.com/v1");

ws.on("error", (err) => {
  console.error("websocket error:", err);
});

ws.on("open", () => {
  console.debug("websocket connected");
  ws.send(JSON.stringify({ type: "authenticate", payload: { token: jwt } }));
});

ws.on("message", (data) => {
  const msg = JSON.parse(data.toString());
  console.debug(`type: ${msg.type}, id: ${msg.id}, time: ${msg.time}`, msg.payload);
});

ws.on("close", (code, reason) => {
  if (code === 4000) {
    console.log(`Error while authenticating: ${code}/${reason}`);
  } else if (code === 4001) {
    console.log(`Error rate limit: ${code}/${reason}`);
  }
});

Limits (same family as HTTP)

ConditionResult
More than 50 concurrent connections from one IPHandshake 429
More than 50 messages per second on one connectionClose 4001

Reconnect

Implement on disconnect: reconnect with backoff. Authenticate again after every new open. Check status.bit2me.com if reconnect loops during an incident.

Message envelope

Every notification has the same shape:

{
  "channel": "notification",
  "id": "string",
  "time": 0,
  "type": "wallet-transaction-v1",
  "payload": {}
}

payload depends on type. The notification channel is the public contract. Do not treat internal as a stable API.

Event catalog: Notification events.

Explorer sample (not the notification socket)

The OpenAPI also shows a BTC testnet explorer snippet. That path is not the account notification stream:

const ws = new WebSocket(`wss://${host}:${port}/v1/explorer/BTCTEST`);

ws.addEventListener("open", () => {
  ws.send(JSON.stringify(["subscribe", "inv"]));
});

ws.addEventListener("message", (message) => {
  const msg = JSON.parse(message.data);
  if (msg[0] === "tx") {
    console.debug("explorer tx:", msg[1]);
  }
});

Close 4002 on that sample means a wrong crypto explorer. Do not use this URL to receive wallet-transaction-v1 or KYC events.

On this page