Parlay Integration

Provide Parlay price quotes and confirmation responses to ProphetX by authenticating with the MM API, seeding events and markets, and subscribing to the parlay WebSocket.

Provide Parlay price quotes and confirmation responses to ProphetX by authenticating with the Market Maker (MM) API, seeding events and markets, and subscribing to the parlay WebSocket.

Overview

  1. Web and mobile clients send parlay quote requests for a list of unique outcomes to the ProphetX backend.
  2. ProphetX broadcasts those requests to integrated Market Makers through WebSocket, and Market Makers return quote responses through a callback REST API.
  3. 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.
  4. After ProphetX receives the confirmation, ProphetX publishes an order.finalized message on the private channel to confirm execution of the parlay.

API documentation

API Documentation

Example Python code: Python Parlay API Integration Guide

Example Golang code: Golang Parlay Integration Guide

Before you start

  1. 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 Market Maker API credentials.
  2. Review the Python or Golang example implementation before you connect to the parlay WebSocket.

Core flow

  1. Exchange a session key by using your Access Key and Secret Key.
  2. Optional: Get your current cash balance.
  3. Seed events and markets for the tournaments you want to trade.

Step 2: 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

  1. Get your WebSocket connection config:
GET /websocket/connection-config

The response returns the appId, cluster, and key values you need to initialize a Pusher-compatible client.

  1. 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.

  1. 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
  1. 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.

  1. 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

  1. price.ask.new delivers a parlay RFQ to Market Makers when ProphetX needs a quote. Send your quote response to the event's callback_url in 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:

FieldDescription
max_riskMaximum quantity the Market Maker is willing to let the requester trade at the quoted price. Treat this as a capacity limit, not the Market Maker's fixed profit/loss commitment.
priceParlay 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_untilExpiration time for the offer as a Unix timestamp in nanoseconds. Set this value to a time greater than 0 and less than 5 seconds after you send the callback. Do not set this value to 0.
estimated_priceOptional per-strike price breakdown for frontend display. Include strike_id and price for each strike you want to show. This value does not need to contain every strike in the parlay and is not required for the offer itself.

How max_risk works

When you POST an offer, max_risk is the most you are willing to let the requester 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 requester at +200." If the requester wins, 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.

  1. price.confirm.new is also documented here for the confirmation callback you send after a participant accepts the quoted price. Use the following payload format:

*Please note - the strike_id and probability are required for all non-SGP (Single Game Parlays). While not required for SGPs it is recommended for a better user experience for the requestor.

{
  "action": "accept",   //or reject
  "confirmed_price": "...",
  "confirmed_quantity": 100.0,  // Optional. If null, no change to the quantity
  "price_probability": [
    {
      "max_risk": 200.0,
      "strikes": [
        {
          "strike_id": "strike_1",
          "probability": 0.5 
        },
        {
          "strike_id": "strike_2",
          "probability": 0.4 
        }
      ],
      "correlation": "..."  // (not determined yet)
    },
    {
      "max_risk": 3000.0,
      "strikes": [
        {
          "strike_id": "strike_1",
          "probability": 0.4
        },
        {
          "strike_id": "strike_2",
          "probability": 0.5 
        }
      ]
    }
  ]
}

Timeout and latency expectations

Use these timeout windows when you implement quote and confirmation handling:

ScenarioExpected timing
Maximum time for a Market Maker to submit a quoteProphetX uses a queue system and processes quotes in the order received. If no Market Maker quotes within 5 seconds, the request times out.
Maximum time for a participant to accept a quote5 seconds.
Timeout for the Market Maker to confirm after the participant accepts5 seconds.
Expected latency for execution to appear in the channel after Market Maker confirmationWithin 1 second.

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:

  1. Parse the next allow timestamp from the message string and sleep until that time. This is the most precise option because you retry when the API allows the next request.
  2. 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.

  1. order.matched is broadcast to all Market Makers connected to the parlay WebSocket. The message includes the latest matched parlay price and filled quantity from all Market Makers. Example message:
{
  "payload": {
    "market_strikes": [...],
    "matched_at": 1759216871383341000,
    "fill_price": 188,
    "filled_quantity": 3.49,
    "parlay_id": "28250d66–30a4–406b-b90c-cd65c96d4c72",
    "timestamp": 1759216871383340000
  }
}
  1. order.finalized is 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
}
  1. order.settled is sent to the order owner after one order 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
}
  1. parlay.settled is sent to the parlay owner 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
}
  1. health_check works the same way as the Trading API. If your client does not receive this message for more than 30 seconds, reconnect to the WebSocket.