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
- Generate API tokens from the sandbox UI.
- Exchange your access key and secret key for a session token by calling the session login endpoint
partner/auth/login. - Store the
access_tokenand send it in theAuthorizationheader asBearer <access_token>. - Seed tournaments and events if you want to bind only to selected tournament topics.
- Install the Python dependencies used in the example:
pip install requests pysherOverview
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 sessionPOST partner/mm/pusher— returns the authorized channels and binding events for your session
There are two primary channel types:
- Broadcast channel
(private-broadcast-service=3-device_type=5)— shared updates sent to all Market Makers - 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_109The 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
- Authenticate with the session login endpoint
partner/auth/login - Build the
Authorization: Bearer <access_token>header - Call
/partner/websocket/connection-config - Create the Pusher client with the returned
keyandcluster - Wait for
pusher:connection_established - Call
partner/mm/pusherwith thesocket_id - Subscribe to the returned broadcast and private channels
- Bind handlers to tournament topics and private events
- 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
Updated 9 days ago
