Parlay Additional Information

Understand Parlay matching, offer validity, WebSocket events, status values, and common errors.

1. The exchange model

The Parlay service is a request-quote-confirm exchange. A participant submits a multi-leg Parlay; Market Makers (SPs) respond with offers; the participant's confirmation triggers a matcher that selects among the offers; matched orders settle after the underlying events resolve.

Participant            Parlay Service                 Market Maker (SP)
  │                          │                                  │
  │ POST /parlays/request    │                                  │
  │─────────────────────────▶│                                  │
  │     { parlayId }         │── price.ask.new ────────────────▶│
  │◀─────────────────────────│                                  │
  │                          │◀── POST /sp/orders/offers ───────│
  │                          │       (one or more offers)       │
  │◀── price.offers.new ─────│                                  │
  │      (aggregated)        │                                  │
  │                          │                                  │
  │ POST /parlays/confirm    │                                  │
  │─────────────────────────▶│                                  │
  │     { parlayId, quantity } │                                │
  │◀─────────────────────────│                                  │
  │                          │── price.confirm.new ────────────▶│
  │◀── order.matched ────────│                                  │
  │      (preliminary)       │                                  │
  │                          │◀── POST /sp/orders/confirmations │
  │◀── order.finalized ──────│                                  │
  │      (terminal signal)   │                                  │


2. Matching engine semantics

The matcher is the most non-obvious piece of the platform. Both audiences need to understand it.

Single-tier per matching round

Within one matching round, the matcher selects only offers at the single best-scored price tier. So if the live pool contains [+6789 $2, +4905 $203], round 1 selects only +6789 $2. The +4905 offer is ignored for that round.

Within a tier: parallel across Market Makers

If multiple Market Makers sit at the best-scored tier, the matcher selects them all and dispatches price.confirm.new to every selected Market Maker simultaneously, followed by order.matched broadcasts in batch. There is no per-Market Maker sequencing within a tier.

Why "tied" offers don't always all win

When multiple offers appear to share the same price, the matcher still ranks them deterministically and takes them in order until the participant's quantity is covered. Anyone past that cutoff is not selected, even at the same displayed price.

Ranking uses a composite score:

  1. Price — primary factor.
  2. max_risk — secondary tiebreaker; a larger offer ranks higher.
  3. Submission time — tertiary tiebreaker; an earlier submission wins.

The matcher locks to the top-ranked offer's price value and only takes other offers whose price is exactly equal at full numeric precision, accumulating max_risk until the requested quantity is covered, then stops.

Practical consequences for SPs whose offers appear to "tie" the winner but don't fill:

FactorEffect on selection
Fractional price gapMost common cause. Prices are compared at full floating-point precision, not at the displayed integer. Two offers that both display as, for example, +561 can be 5.6105 and 5.6098 underneath; the higher value wins outright on the primary key and no tiebreaker is consulted.
max_riskIf prices are exactly equal at full precision, the larger max_risk ranks first.
Submission timeOnly matters when both price and max_risk are exactly equal; an earlier submission wins.
Random selection / Market Maker rotation / fairness layerNone exist. Ranking is purely deterministic.
Market Maker reputation or fill historyNot a factor. The only inputs are price, max_risk, and timestamp.
Split fillsIf the top Market Maker's max_risk is smaller than the participant's quantity, the next-ranked Market Makers fill the remainder in score order until the quantity is covered. Offers past that cutoff are not filled.

When investigating a "tied but didn't fill" case, check in this order:

  1. Compare the full-precision price values, not the displayed integer.
  2. If truly equal, compare max_risk.
  3. If those also match, compare submission time.

If you have a specific parlay ID where this happened, ProphetX can look up the raw offer data and confirm which axis decided the order.

Across tiers: sequential, gated on full accept

After every Market Maker confirmation, the platform tracks pending orders for the round. When all Market Makers in the current round have responded, if the round was a full accept, the matcher runs again on the remaining quantity and selects the now-top tier.

Round-N outcomeTrigger re-match?
Full accept by all Market Makers at the tierYes — next-best tier gets price.confirm.new
Partial accept (confirmed_quantity < requested_quantity)No — finalize and refund the remainder
RejectNo — finalize and refund the remainder
Timeout (no callback)No — finalize and refund the remainder

So tiers do traverse sequentially across rounds, but only when each round fully accepts. Any short fill, reject, or timeout terminates the chain.

Live pool, not snapshot

The matcher selects against the live offer pool at confirmation time, not against the offers the participant saw earlier. A better-priced offer that lands between offers-fetch and /parlays/confirm will be used. Implications:

  • Participants can receive price improvement beyond the UI display.
  • Market Makers that revise a quote with a better price before confirmation are selected over older quotes from themselves or competitors.

Latest quote supersedes earlier

When a single SP submits multiple quotes for the same parlay, the matcher only considers the most recent one. Older quotes from the same SP stay in the pool but are skipped at match time.


3. valid_until constraints

Every offer carries a valid_until (Unix nanoseconds). Server-enforced bounds at offer submission:

  • Min: 5 seconds in the future ("valid_until must be greater than 5s").
  • Max: 10 minutes in the future ("valid_until must be less than 10m0s").

There is no expiry event broadcast when an offer becomes stale. Expired offers are silently filtered out at match time. Participants and UIs must run client-side timers from valid_until; Market Makers must submit a new quote if they need extended validity.


4. WebSocket events

EventAudienceMeaning
price.ask.newMarket MakerNew Parlay request — quote it.
price.offers.newParticipant / ISVAggregated Market Maker offers, grouped by price.
price.confirm.newMarket MakerConfirmation requested for a selected offer.
order.matchedpublic + Market MakerPreliminary. The matcher selected a counterparty; the Market Maker has not confirmed yet.
order.finalizedparticipant + Market MakerTerminal per order. The Market Maker callback resolved as accept or reject.
order.settledparticipant + Market MakerCash balance settlement posted.
parlay.processingparticipantMatcher running.
parlay.finalizedparticipantTerminal per Parlay. All orders resolved.
parlay.settledparticipantAll settlement complete.
refund.processedparticipantRefund posted, such as for open quantity.

order.matched is preliminary

order.matched fires the moment the matcher selects a counterparty — at the same time price.confirm.new is dispatched to the SP, before the SP has confirmed via callback. The order's persisted status at broadcast time is sent_confirmation, not matched. For terminal "trade is done" signals, listen for order.finalized / parlay.finalized.


5. Status enums

Order statuses:

StatusMeaning
inactiveOrder created, confirmation not yet sent.
sent_confirmationprice.confirm.new dispatched to a Market Maker.
openParticipant-side order, cash balance deducted, awaiting a match.
matchedMarket Maker callback accepted.
finalizedReady for settlement.
pending_payoutSettlement calculated, payout queued.
payout_submittedPayout sent to the cash balance.
settledCash balance transaction confirmed.
rejectedMarket Maker rejected, or liquidity was insufficient.
void / failedTerminal failure.

Offer statuses: pendingconfirmingpartial_confirmed / confirmed / rejected.

Match statuses (parlay-level): pendingfinalized / failed.

Settlement statuses (Parlay level): tbdprofit / loss / push / void.


6. Common errors

error codeHTTPWhen
invalid_params400Validation failed.
invalid_legs_combination400Legs not accepted (correlated, banned, etc.).
no_valid_market_lines400One or more legs reference unsupported lines.
parlay_already_exists400Re-submitting a confirmed parlay.
parlay_not_valid400Parlay expired or not found.
confirmation_arrive_too_late400Confirm callback after the offer's valid_until.
order_not_valid400Confirming an order in the wrong state.
wallet_insufficient_funds / insufficient_balance400 / 402User / ISV wallet rejected.
unauthorized401Missing/invalid auth header.
sp_banned403Market Maker is on a ban list.
not_found404Resource lookup failed.
request_timeout408Upstream timeout.
no_offers_found412(Public sync) no SPs quoted in time.
parlay_disabled503Parlays disabled by config / ops.

7. Failed to Validate Price Probability

When you quote Parlays, provide a strike_id and probability for each strike in every non-SGP (single-game Parlay). SGPs do not require probabilities, but including them improves clarity for the participant.

ErrorMeaning
empty price probability arrayThe strikes were not given strike_id values or probabilities in the quote.
invalid probabilityThe given probabilities do not equal the requested price.

8. Outcome IDs (commonly seen)

IDMeaning
4Home
5Away
12Over
13Under

Full list owned by the ProphetX Markets team.


Did this page help you?