WebSocket Integration

Connect to ProphetX WebSockets with Pusher, authorize channels, and subscribe to real-time event, market, and order updates

Connect to ProphetX WebSockets, authorize your channels, and subscribe to real-time event, market, and order updates.

Before you begin

  1. Generate API tokens from the sandbox UI.
  2. Exchange your access key and secret key for a session token by calling the session login endpoint partner/auth/login.
  3. Store the access_token and send it in the Authorization header as Bearer <access_token>.
  4. Seed tournaments and events if you want to bind only to selected tournament topics.
  5. Install the Python dependencies used in the example:
pip install requests pysher

Overview

ProphetX WebSockets are powered by Pusher. Use them to receive event updates, market liquidity updates, and private order updates without polling the REST API repeatedly.

This flow uses two endpoints:

  • GET /partner/websocket/connection-config — retrieves the Pusher connection configuration for your participant session
  • POST partner/mm/pusher — returns the authorized channels and binding events for your session

There are two primary channel types:

  1. Broadcast channel (private-broadcast-service=3-device_type=5) — shared updates sent to all Market Makers
  2. Private channel (private-service=3-device_type=5-user=<partnerId>) — participant-specific updates related to your own orders and account activity

You do not need to construct these channel names manually. Call partner/mm/pusher and use the returned channel metadata.

Refresh the connection configuration at least once every 30 minutes. If the configuration changes, reconnect by using the new values.

Step 1: Build the authentication header

Use your session token in the Authorization header for both REST and WebSocket authorization requests.

def __get_auth_header(self) -> dict:
    return {
        'Authorization': f'Bearer {self.mm_session["access_token"]}',
    }

Step 2: Retrieve the connection configuration

Call the current connection-config endpoint /partner/websocket/connection-config to retrieve the Pusher connection details.

def _get_connection_config(self):
    """
    Get websocket connection configurations.
    We use Pusher as the websocket service, and only authenticated channels are supported.
    Retrieve the latest connection configuration at least once every thirty minutes.
    """
    connection_config_url = urljoin(self.base_url, config.URL['websocket_config'])
    connection_response = requests.get(connection_config_url, headers=self.__get_auth_header())
    if connection_response.status_code != 200:
        logging.error("failed to get connection configs")
        raise Exception("failed to get channels")
    return connection_response.json()

Step 3: Retrieve the authorized channels

After the WebSocket connection is established, call partner/mm/pusher with the socket_id to retrieve the channels and binding events available to your session.

def _get_channels(self, socket_id: float):
    """
    Get the channels and topics that this API user is allowed to subscribe to.
    Even though there are public and private channels, the channel id is unique for each API user.
    """
    auth_endpoint_url = urljoin(self.base_url, config.URL['mm_auth'])
    channels_response = requests.post(
        auth_endpoint_url,
        data={'socket_id': socket_id},
        headers=self.__get_auth_header(),
    )
    if channels_response.status_code != 200:
        logging.error("failed to get channels")
        raise Exception("failed to get channels")
    channels = channels_response.json()
    return channels.get('data', {}).get('authorized_channel', [])

Step 4: Connect to Pusher and subscribe

Create the Pusher client, wait for the connection handshake, retrieve the authorized channels, and then bind the events you want to process.

In this example, the client subscribes to all tournament updates but only binds handlers for tournaments already seeded in self.my_tournaments.

def subscribe(self):
    """
    1. Get Pusher connection configurations
    2. Connect to Pusher by providing authentication credentials
    3. Wait for the websocket handshake
    4. Subscribe to public channels on selected topics
    5. Subscribe to private channels on all available topics
    """
    connection_config = self._get_connection_config()
    key = connection_config['key']
    cluster = connection_config['cluster']

    auth_endpoint_url = urljoin(self.base_url, config.URL['mm_auth'])
    auth_header = self.__get_auth_header()
    auth_headers = {
        "Authorization": auth_header['Authorization'],
        "header-subscriptions": '''[{"type":"tournament","ids":[]}]''',
    }

    self.pusher = pysher.Pusher(
        key=key,
        cluster=cluster,
        auth_endpoint=auth_endpoint_url,
        auth_endpoint_headers=auth_headers,
    )

    def public_event_handler(*args, **kwargs):
        print("processing public, Args:", args)
        print(f"event details {base64.b64decode(json.loads(args[0]).get('payload', '{}'))}")
        print("processing public, Kwargs:", kwargs)

    def private_event_handler(*args, **kwargs):
        print("processing private, Args:", args)
        print(f"event details {base64.b64decode(json.loads(args[0]).get('payload', '{}'))}")
        print("processing private, Kwargs:", kwargs)

    def connect_handler(data):
        socket_id = json.loads(data)['socket_id']
        available_channels = self._get_channels(socket_id)
        broadcast_channel_name = None
        private_channel_name = None
        private_events = None

        for channel in available_channels:
            if 'broadcast' in channel['channel_name']:
                broadcast_channel_name = channel['channel_name']
            else:
                private_channel_name = channel['channel_name']
                private_events = channel['binding_events']

        broadcast_channel = self.pusher.subscribe(broadcast_channel_name)
        private_channel = self.pusher.subscribe(private_channel_name)

        for t_id in self.my_tournaments:
            event_name = f'tournament_{t_id}'
            broadcast_channel.bind(event_name, public_event_handler)
            logging.info(f"subscribed to public channel, event name: {event_name}, successfully")

        for private_event in private_events:
            private_channel.bind(private_event['name'], private_event_handler)
            logging.info(f"subscribed to private channel, event name: {private_event['name']}, successfully")

    self.pusher.connection.bind('pusher:connection_established', connect_handler)
    self.pusher.connect()

Step 5: Understand the event binding pattern

The broadcast channel can carry updates for many tournaments. In the example above, the client binds only to tournament topics already present in self.my_tournaments.

For example, if MLB has tournament ID 109, the code binds to:

tournament_109

The private channel uses the binding_events returned by partner/mm/pusher, so you can bind all private updates without hard-coding their names.

What the handlers receive

Both public and private handlers receive payloads that can be Base64-decoded from the message body:

print(f"event details {base64.b64decode(json.loads(args[0]).get('payload', '{}'))}")

Use this pattern to inspect the raw event details while you build your integration.

Complete flow summary

  1. Authenticate with the session login endpoint partner/auth/login
  2. Build the Authorization: Bearer <access_token> header
  3. Call /partner/websocket/connection-config
  4. Create the Pusher client with the returned key and cluster
  5. Wait for pusher:connection_established
  6. Call partner/mm/pusher with the socket_id
  7. Subscribe to the returned broadcast and private channels
  8. Bind handlers to tournament topics and private events
  9. Refresh the connection configuration every 30 minutes

Troubleshooting

The connection config request fails

Verify that you have already exchanged your access key and secret key through partner/auth/login, and confirm that your Authorization header uses the Bearer prefix with the current access_token.

The WebSocket connects, but no channels are returned

Make sure you call partner/mm/pusher only after the pusher:connection_established event fires and gives you a valid socket_id.

I am connected, but I am not receiving tournament updates

Check that your client seeded tournaments first and that you are binding to the correct topic name format: tournament_<tournament_id>.

Private updates are missing

Use the binding_events returned for the private channel and bind each event name dynamically instead of hard-coding private topics.

The connection becomes stale after running for a while

Retrieve the latest connection configuration at least once every 30 minutes. If the returned configuration changes, reconnect the Pusher client by using the new values.

Related guides