Parlay Integration
Provide Parlay price quotes and confirmation responses through the Market Maker API and Parlay WebSocket.
Provide Parlay price quotes and confirmation responses to ProphetX by authenticating with the Trading API, seeding events and markets, and subscribing to the parlay WebSocket.
Overview
- Web and mobile clients send parlay quote requests for a list of unique outcomes to the ProphetX backend.
- ProphetX broadcasts those requests to integrated Market Makers through WebSocket, and Market Makers return quote responses through a callback REST API.
- When a participant accepts a price, ProphetX sends a private confirmation request to Market Makers that can satisfy the order. Market Makers then send their confirmation response through a callback REST API.
- After ProphetX receives the confirmation, ProphetX publishes an
order.finalizedmessage on the private channel to confirm execution of the parlay.
API documentation
Example Python code: Python Parlay API Integration Guide
Example Golang code: Golang Parlay Integration Guide
Before you start
- Follow the first three steps in Trading API Integration to get your Trading API credentials and ingest events and markets. The Parlay API uses the same Trading API credentials.
- Review the Python or Golang example implementation before you connect to the parlay WebSocket.
Core flow
- Exchange a session key by using your Access Key and Secret Key.
- Optional: Get your current cash balance.
- Seed events and markets for the tournaments you want to trade.
- Register the strikes you can quote on. See Register supported strikes.
Register supported strikes
ProphetX only sends you RFQs for strikes you've explicitly registered. Register a strike after you've seeded its event and market, and before you expect to receive quote requests for it.
Add supported strikes
POST /supported-strikes
Authorization: Bearer <your-token>
Content-Type: application/json{
"supported_strikes": ["strike_1", "strike_2"]
}supported_strikes is a flat array of strike_id strings — no nested objects or market/event wrapper. Use the same strike_id values you receive elsewhere in the API, such as on estimated_price and price_probability.strikes entries in the callback payloads described below.
Revoke supported strikes
DELETE /supported-strikes
Authorization: Bearer <your-token>
Content-Type: application/json{
"supported_strikes": ["strike_1"]
}List your supported strikes
GET /supported-strikes?limit=100&token=<pagination-token>{
"supported_strikes": ["strike_1", "strike_2"],
"limit": 100,
"token": "<next-page-token>"
}token is a pagination cursor returned by a previous call. Omit it on your first request.
Request size limit
Each POST or DELETE call accepts a limited number of supported_strikes entries per request. If you exceed it, the API returns invalid_params with a message naming the limit — batch your calls to stay under it.
Connect to the Parlay WebSocket
See subscribe(self) in the Python example and ListenForMessages() in the Golang example. The parlay WebSocket exposes seven topics.
RFQ connection flow
- Get your WebSocket connection config:
GET /websocket/connection-configThe response returns the appId, cluster, and key values you need to initialize a Pusher-compatible client.
- Connect and register your socket.
Use any Pusher-compatible client to connect with the config from step 1. After the client connects, register the socket with ProphetX to get signed auth tokens for your private channels:
POST /websocket/register
Authorization: Bearer <your-token>
Content-Type: application/json{
"socket_id": "<socket-id-from-pusher-client>"
}The response includes signed auth tokens for the private channels your client can subscribe to.
- Subscribe to the RFQ broadcast channel.
Use the auth token from the registration response to subscribe to this channel:
private-broadcast-service=4-device_type=5- Listen for RFQ events.
Bind to the price.ask.new event on the RFQ broadcast channel. Each event payload includes the parlay ID, creator ID, requested quantity, market lines, tournament IDs, and a unique callback URL:
{
"parlay_id": "abc-123",
"creator_id": "user-456",
"stake": 100.0,
"market_lines": [...],
"created_at": 1718000000,
"tournament_ids": [42],
"callback_url": "https://..."
}Use payload field names exactly as received. For example, the RFQ event currently includes stake and market_lines; callback payloads use the documented callback field names below.
- Submit your quote.
POST your price quote to the callback_url included in each RFQ event. The callback URL is unique per request and has a short TTL, so respond promptly.
WebSocket topics and callbacks
price.ask.newdelivers a Parlay RFQ to Market Makers when ProphetX needs a quote. Send your quote response to the event'scallback_urlin the following callback payload format:
{
"parlay_id": "price_quote_request['parlay_id']",
"offers": [
{
"valid_until": 1723564800000000000,
"price": 1000,
"max_risk": 200,
"estimated_price": [
{
"strike_id": "strike_1",
"price": 200
},
{
"strike_id": "strike_2",
"price": 220
}
]
}
]
}Quote callback fields
Use these rules when you send the /orders/offers callback:
| Field | Description |
|---|---|
max_risk | Maximum quantity the Market Maker is willing to let the participant trade at the quoted price. Treat this as a capacity limit, not the Market Maker's fixed profit/loss commitment. |
price | Parlay price from the Market Maker's perspective. Use American price format. Do not follow normal price ladder, and can be set to any given price. |
valid_until | Expiration time for the offer as a Unix timestamp in nanoseconds. Set this value to a time greater than 5 with latency padding after you send the callback. Do not set this value to 5. |
estimated_price | Per-strike price breakdown for frontend display. Include strike_id and price. Required for uncorrelated parlays. |
How max_risk works
max_risk worksWhen you POST an offer, max_risk is the most you are willing to let the participant trade against you at your quoted price. It is a ceiling on how much action you will take at that price.
For example, if you quote +200 and set max_risk to 100, you are saying: "I will accept up to a $100 order from the participant at +200." If the participant receives a profit, you would owe $200 in profit. If they only trade $50, you would owe $100 in profit.
Do not treat max_risk as the dollar amount you are putting up yourself. It is a capacity limit for the quote.
price.confirm.newis the confirmation callback you send after a participant accepts the quoted price. Setactiontoacceptorreject. Ifconfirmed_quantityisnull, the quantity does not change.
Provide strike_id and probability for every non-SGP (single-game Parlay) strike. SGPs do not require probabilities, but including them improves the participant experience.
{
"action": "accept",
"confirmed_price": "...",
"confirmed_quantity": 100.0,
"price_probability": [
{
"max_risk": 200.0,
"strikes": [
{
"strike_id": "strike_1",
"probability": 0.5
},
{
"strike_id": "strike_2",
"probability": 0.4
}
]
},
{
"max_risk": 3000.0,
"strikes": [
{
"strike_id": "strike_1",
"probability": 0.4
},
{
"strike_id": "strike_2",
"probability": 0.5
}
]
}
]
}Calculating price_probability
price_probabilityprice_probability in the price.confirm.new callback must reflect the actual probability of each leg, so ProphetX can validate that your confirmed parlay price is fair and can auto-settle correctly if a leg pushes. Getting this right takes two steps.
1. Convert each leg's odds to an implied probability. For American odds:
- Positive odds (
+150):probability = 100 / (odds + 100) - Negative odds (
-180):probability = -odds / (-odds + 100)
2. Multiply the legs together. For independent legs, the parlay's overall probability is the product of each leg's individual probability — this is what the product of your submitted strikes[].probability values must reconstruct.
Use the requester's side, not your own confirmed price, when computing that product. This is the part that's easy to get backwards: the probability you submit must correspond to the odds opposite your own confirmed_price/confirmed_odds, not the value itself. A real example that tripped this up: a Market Maker confirmed a 5-leg parlay at +4146 and submitted leg probabilities whose product came out to 0.023553264 — which is the implied probability of +4146 (0.023551578), matching almost exactly. That still came back invalid_probability, because the correct calculation should have used -4146 (the mirror of the confirmed price) as the target, not +4146 itself.
If your probabilities don't reconstruct the target closely enough, you'll see invalid_probability on the confirmation — but the trade still executes. It isn't a hard rejection. The only consequence is that ProphetX can't auto-settle the order if one of its legs pushes; ProphetX operations settles it manually instead. Still worth getting right, since manual settlement is slower and adds operational risk on both sides.
SGP (single-game parlay) legs don't require a probability — you can omit them from strikes[] — but including them still improves what participants see, so provide them when you can.
Timeout and latency expectations
Use these timeout windows when you implement quote and confirmation handling:
| Scenario | Expected timing |
|---|---|
| Maximum time for a Market Maker to submit a quote | ProphetX uses a queue system and processes quotes in the order received. If no Market Maker quotes within 10 seconds, the request times out. |
| Maximum time for a participant to accept a quote | 10 seconds. |
| Timeout for the Market Maker to confirm after the participant accepts | 10 seconds. |
| Expected latency for execution to appear in the channel after Market Maker confirmation | Within 1 second. |
Timeouts are counted the same way as rejects. If a Market Maker accumulates too many timeouts or rejects, ProphetX temporarily suspends that Market Maker for 15 minutes.
Rate limit handling
The Parlay API rate limit is 300 requests per second per token. If a request returns 429, the response does not include a Retry-After header or X-RateLimit-* headers.
Instead, the response body includes the next allowed retry timestamp in the message string:
{
"error": "rate_limit_reached",
"message": "rate limit reached, please send only 300 requests/second, next allow 2026-06-18T20:18:37Z"
}Use one of these retry strategies:
- Parse the
next allowtimestamp from themessagestring and sleep until that time. This is the most precise option because you retry when the API allows the next request. - Use exponential backoff with jitter if you do not want to parse the message. Start around 1 second, double the wait time after each attempt, cap the wait around 30 seconds, and add a random offset of about ±25%.
Exponential backoff with jitter is less precise, but it helps prevent a thundering herd problem where many clients hit the limit, read the same next allow timestamp, and retry at the same moment.
order.matchedis sent on the public broadcast channel and to the selected Market Maker's private channel as soon as the matcher selects that Market Maker — at the same time asprice.confirm.new, before the Market Maker has confirmed via callback. This is preliminary, not a final match: the order's persisted status at this point issent_confirmation. For the final, executed state, listen fororder.finalizedbelow. Example message:
{
"payload": {
"market_lines": [...],
"matched_at": 1759216871383341000,
"fill_price": 188,
"filled_quantity": 3.49,
"parlay_id": "28250d66-30a4-406b-b90c-cd65c96d4c72",
"timestamp": 1759216871383340000
}
}order.finalizedis sent after ProphetX receives the confirmation callback. This message confirms that the parlay order has been executed on the ProphetX side. Example payload:
{
"payload": {
"confirmed_price": -100000,
"confirmed_quantity": 1349.049722844427,
"price": -100000,
"order_uuid": "01977e64-756b-7a75-b218-f0a0e4032004",
"parlay_id": "23851166-3e18-4b3c-aea7-16de1b114bb8",
"status": "finalized",
"updated_at": 1750172203426483200
},
"timestamp": 1750172203428558300
}order.settledis sent to the Market Maker after one of their orders in the Parlay reaches settlement.
{
"payload": {
"order_uuid": "01991588-274d-70be-ac0a-cf876677d57f",
"parlay_id": "fdd7e3fb-ef50-42eb-9d73-43d3d7f1390f",
"settlement_status": "profit",
"settled_at": 1757002869231213000,
"profit": 150
},
"timestamp": 1757002869231213000
}parlay.settledis sent to the participant after all orders in the Parlay reach settlement.
{
"payload": {
"parlay_id": "fdd7e3fb-ef50-42eb-9d73-43d3d7f1390f",
"settlement_status": "profit",
"settled_at": 1757002869231213000,
"profit": 150
},
"timestamp": 1757002869231213000
}health_checkworks the same way as the Trading API. If your client does not receive this message for more than 30 seconds, reconnect to the WebSocket.
Updated 6 days ago
