User Manual
This guide is for external MBS Ticket 3 SDK clients that send requests to the MTS Gate Sandbox instead of a live MBS environment.
How you connect: use the Java MBS SDK to build a TicketRequest and send it over the WebSocket session the SDK opens for you.
The SDK serializes builders to Ticket 3.0 JSON. Download the request and response schemas from JSON Schemas. Sandbox routing still depends on specific endCustomer.id, event, and selection values called out in each scenario.
Covered scenarios:
single_accepted
accumulator_prematch
odds_format_accepted
odds_format_rejected
rejected_ticket_exception
rejected_odds_too_high
rejected_market_not_active
rejected_liability_limit
outright_not_active
single_live_async
alt_odds
alt_stake
reoffer
promo_stake
promo_boosted_odds
promo_payout_modifier
system_prematch
ways_prematch
custom_bet
external_bet
multi_bet
system_banker_ways
cancel_full_accepted
cancel_full_rejected
cancel_partial
cashout_full_accepted
cashout_full_rejected
cashout_partial
max_stake
ticket_inform
external_settlement
fallback
Each page has build steps, Java examples, wire JSON, field tables, expected replies, and common mistakes.
Using the Java MBS SDK
Artifact: com.sportradar.mbs.sdk:mbs-sdk
Package root: com.sportradar.mbs.sdk.
Configure and connect
Create MbsSdkConfig with the WebSocket and OAuth values Sportradar provides for your sandbox (or production) integration, then connect once and reuse the client:
OAuth token required before WebSocket
A valid access token must be obtained from the OAuth token endpoint before the WebSocket connection can be established. With the Java SDK, connect() performs that OAuth step using the auth server, client id, client secret, and audience you pass in MbsSdkConfig, then opens the WebSocket. Connection setup fails if token acquisition fails. See Connectivity for the token request details.
Use 9985 as operatorId in sandbox examples (same value as Ticket 3.0 sample fixtures). The sandbox ignores operatorId for routing and reply building; it still must be present on the Ticket 3.0 envelope.
The SDK is thread-safe; one instance can serve the application lifespan. It handles OAuth and the WebSocket; you do not open the socket yourself.
Close the SDK
When the MbsSdk instance is no longer needed, tear it down so it can release the resources it holds (WebSocket connections, background workers, buffers):
Call close() during application shutdown (or when you permanently stop using this client). After close(), do not call sendTicket on that instance — you will get SdkNotConnectedException. Prefer a single long-lived client with one close() at the end over opening and closing per request.
What the SDK fills for you
When you call sendTicket, the SDK wraps your TicketRequest in the Ticket 3.0 envelope:
operation
SDK → "ticket-placement"
version
SDK → "3.0"
operatorId
From MbsSdkConfig (9985 in examples; sandbox ignores it for routing)
correlationId
Generated by the SDK
timestampUtc
Set by the SDK
content
Your TicketRequest (JSON type "ticket")
You only build the ticket content with builders (ticketId, context, bets).
Build the request
Ticket / context / end customer
TicketRequest.newBuilder(), TicketContext.newBuilder(), EndCustomer.newBuilder().setId(...)
Channel (example)
Channel.newMobileChannelBuilder()
Bet / bet context
Bet.newBuilder(), BetContext.newBuilder().setOddsChange(OddsChange.HIGHER)
Ticket ref
TicketRef.newAltStakeTicketRefBuilder(), TicketRef.newReofferTicketRefBuilder()
UF / accumulator / system / ways
Selection.newUfSelectionBuilder(), newAccumulatorSelectionBuilder(), newSystemSelectionBuilder(), newWaysSelectionBuilder()
Custom bet / external / odds-boost / payout-modifier
newUfCustomBetSelectionBuilder(), newExtSelectionBuilder(), newOddsBoostSelectionBuilder(), newPayoutModifierSelectionBuilder()
Odds formats
Odds.newDecimalOddsBuilder(), newFractionalOddsBuilder(), newMoneylineOddsBuilder(), newHongKongOddsBuilder(), newIndonesianOddsBuilder(), newMalayOddsBuilder()
Stakes
Stake.newCashStakeBuilder(), newBonusStakeBuilder(), newFreeStakeBuilder()
Cancel / cashout / max-stake / inform / ext-settlement
CancelRequest, CashoutInformRequest / CashoutRequest, MaxStakeRequest, TicketInformRequest, ExtSettlementRequest
Send
getTicketProtocol().sendTicket / sendCancel / sendCashoutInform / sendCashout / sendMaxStake / sendTicketInform / sendExtSettlement
Reply
Matching *Response (getStatus(), getCode(), getMessage(), …)
Builders do not validate required fields locally. Missing Ticket 3.0 / sandbox fields fail at the server or route to the wrong scenario.
Send and read the reply
TicketResponse getter
Meaning in these scenarios
getStatus()
AcceptanceStatus.ACCEPTED (wire: "accepted")
getCode()
0
getMessage()
"accepted" (sandbox)
getTicketId()
Echo of your request ticket id
getSignature()
Sandbox signature string
getBetDetails()
Per-bet / selection details (selection echoed)
Shared sandbox constants
Prematch match (typical first event)
sr:match:15050881
First UF / nested UF eventId
Outright event
sr:season:55413
UF eventId for outright reject
External event
ext:match:39999
ExtSelection.setEvent
Prematch / live product id
"3" / "1"
UfSelection.setProductId
Placement customers (examples)
customer, customer_rejected_*, customer_alt_*, customer_promo, customer_live_async_accepted, customer_reoffer_*, customer_accumulator, customer_system, customer_ways, customer_custom_bet, customer_external, customer_multi_bet, customer_system_banker, customer_inform
EndCustomer.setId
Cancel / cashout customers
customer_cancel_*, customer_cashout_*
Transaction meta.punter.id or magic ticketId
Magic cancel ticket ids
mock_cancel_accepted, mock_cancel_rejected, mock_cancel_partial
CancelDetails ticket id
Magic cashout ticket ids
mock_cashout_full, mock_cashout_rejected, mock_cashout_partial
Cashout details ticket id
Max-stake amounts (sandbox)
2872885 EUR (UF) / 1950000 EUR (accumulator)
Reply stake amount
Routing facets
End customer id
TicketContext → EndCustomer.id (placement / inform)
Selects scenario family
Event id
First selection event (UF / nested / external)
Usually sr:match:15050881; outright / external use their own ids
Odds change
BetContext.oddsChange
Required higher for alt_odds
Ref type
TicketContext.ref (alt-stake / reoffer)
Alt-stake step 2 and reoffer routes
Stake type
First Stake subtype
cash → facet cash; bonus; free → facet freebet
Selection type
Top-level Selection subtype (or multi-bet / system-banker shape)
uf, accumulator, system, ways, custom-bet, external, odds-boost, payout-modifier, …
Odds format
Selection Odds subtype
decimal, fractional, american (moneyline), hong_kong, indonesian, malay
Cancel / cashout
Full vs partial + percentage + punter / magic ticket id
See cancel_* / cashout_* pages
Ticket-inform validation code
betValidations[0].code
Must be 3 for ticket_inform
How routing works
The sandbox does not validate operators or call upstream MTS. It derives routing facets from your ticket and picks a deterministic scenario.
Schema/ticket content checklist
Even though the SDK fills the envelope, the ticket body must still satisfy Ticket 3.0:
ticketId
Required string (1–128 chars)
context.limitId
Required integer ≥ 1 (client limit id from Sportradar)
context.channel
Required (e.g. mobile + lang)
context.endCustomer.id
Required for sandbox routing here; 1–36 chars: [0-9A-Za-z#\-_]
Each UF selection
productId, eventId, marketId, outcomeId, odds
Each cash stake
amount, currency
Error responses
For placement scenarios, the failure you will almost always see when routing misses is a rejected ticket reply, not a separate Ticket 3.0 error-reply. Cancel / cashout / max-stake / inform / ext-settlement miss the same way (code −999 on their reply types) — see fallback.
Rejected ticket reply (no matching scenario) — primary sandbox failure
When the ticket does not match a sandbox route (wrong endCustomer.id, event, selection type, odds format, etc.), the sandbox returns a ticket-reply with rejected status and code -999.
Message text in this codebase:
No matching sandbox scenario found
(There is no string like “could not route to a valid Scenario” in the sandbox.)
content.type
ticket-reply
content.status
rejected
content.code
-999
content.message
No matching sandbox scenario found
content.ticketId
Echo of your request ticket id
content.signature
sandbox-signature
Java SDK: sandbox outcomes arrive as a normal TicketResponse. Handle them with if on the response (not try-catch). Use try-catch for internal SDK/transport failures.
Example wire JSON:
Typical causes of a routing miss:
Wrong end customer
customer vs customer_accumulator swapped
Wrong first event
Not sr:match:15050881
Wrong selection type
UF when you meant accumulator (or the reverse)
Wrong odds format
Non-decimal odds while expecting these accepted routes
Schema validation error (narrow case)
The sandbox does implement an internal RESP_ERROR_REPLY path, but only when placement has more than one bet, or more than one top-level selection on the first bet (and the customer is not customer_multi_bet / customer_system_banker). That path is not used for “unknown scenario” routing.
Response content type
RESP_ERROR_REPLY
error.errorCode
400
error.errorMessage
The mts_sandbox currently only supports single-bet, single-selection tickets.
If the gateway in front of the sandbox delivers that as Ticket 3.0 error-reply, the Java SDK does not return a TicketResponse. It fails the call with ServerErrorResponseException (as ExecutionException.getCause() when using blocking sendTicket):
This is not ProtocolInvalidRequestException — that exception is for protocol “request not processed” cases, not for a deserialized error-reply. For wrong routing keys, expect the rejected TicketResponse above, not this path.
Notes for these scenarios:
A single top-level accumulator with nested UF legs is valid (one top-level selection).
Putting two UF selections as siblings on the same bet (without an accumulator wrapper) hits this schema validation for ordinary customers. Only
customer_multi_bet(multiple bets) andcustomer_system_banker(multiple top-level selections) are exempt.
How to tell the failures apart
Sandbox path
fallback_scenario
sandbox_api_error_response
Meaning
Ticket OK enough to route, but no table row matched
Multi-bet / multi top-level selection rejected before routing
Typical result for SDK users
TicketResponse with AcceptanceStatus.REJECTED, code -999, message No matching sandbox scenario found — handle with if
If exposed as Ticket 3.0 error-reply: ServerErrorResponseException (getErrorCode() / getMessage()) — handle in catch
Handling sandbox vs SDK errors
Keep these two layers separate:
Sandbox (accepted / rejected ticket)
TicketResponse returned successfully
if on getStatus(), getCode(), getMessage()
SDK / transport
Exception (usually wrapped in ExecutionException from blocking sendTicket)
try / catch
sendTicket is blocking and declared to throw ExecutionException and InterruptedException. SDK failures complete the async call exceptionally, so with the blocking API they appear as ExecutionException.getCause().
SdkNotConnectedException
sendTicket before connect(), or after close() / dispose
ProtocolTimeoutException
No matching WebSocket reply within the configured timeout
ServerErrorResponseException
Server returned Ticket 3.0 error-reply (SDK maps it to this exception; includes sandbox schema-validation if delivered as error-reply)
ProtocolInvalidRequestException
Protocol layer reported the request was not processed (distinct from error-reply)
ProtocolSendFailedException
Send over the WebSocket failed
Always call mbsSdk.connect() before sendTicket, and keep the session open while waiting for the reply. Do not use try-catch to detect sandbox rejection (-999); that is a successful protocol round-trip with a rejected TicketResponse.
Quick reference
odds_format_accepted
customer + non-decimal odds
fractional / moneyline / HK / indonesian / malay
ACCEPTED
odds_format_rejected
customer_rejected_odds + non-decimal
same formats (no malay reject)
REJECTED −421
promo_*
customer_promo
bonus/free stake or odds-boost / payout-modifier
ACCEPTED
system_prematch / ways_prematch / custom_bet / external_bet
matching customer_*
system / ways / uf-custom-bet / external
ACCEPTED
multi_bet / system_banker_ways
customer_multi_bet / customer_system_banker
multi-bet / multi top-level selection
ACCEPTED
Cancel / cashout
customer_cancel_* / customer_cashout_* or magic ids
sendCancel / sendCashoutInform
accepted or coded reject
external_settlement
customer_external (punter)
sendExtSettlement, zero cash payout
ACCEPTED
(no route match)
unmatched combination
—
rejected, code −999, No matching sandbox scenario found
Last updated
Was this helpful?