beexar
Beexar operator SDK for Python. Launch game sessions, and serve the four seamless-wallet callbacks the platform calls during play.
Zero runtime dependencies — standard library only. Python 3.9+.
pip install beexar
Full docs: https://docs.beexar.com · OpenAPI: https://docs.beexar.com/api-reference/
The integration in one picture
There are two halves, and the second one is the work.
you ── POST /api/v1/softswiss/launcher/real ──▶ Beexar (Client)
│
player plays │
▼
your wallet ◀── POST /balance /betwin /rollback /finish ── Beexar (WalletServer)
Half 1 — launching a game
from beexar import Client
beexar = Client(os.environ["BEEXAR_CASINO_ID"], os.environ["BEEXAR_API_SECRET"])
launch_url = beexar.launch_real({
"game": "dice",
"account": {"id": "player_123", "currency": "EUR"},
"locale": "en",
})
# put launch_url in an iframe
launch_demo() does the same on a virtual balance and makes no wallet calls.
list_games() returns the catalogue enabled for you.
Half 2 — serving the wallet
Implement four methods against your ledger. Everything else — signature verification, parsing, validation, the error envelope — is handled.
from beexar import BetWinResult, BetWinTransactionResult, WalletError, WalletServer
class Wallet:
def bet_win(self, request, context):
with self.db.transaction(): # ONE transaction. See "The boundary".
results = []
for t in request.transactions:
seen = self.lookup(t.id_provider)
if seen:
results.append(BetWinTransactionResult(t.id_provider, seen.id))
continue
if self.is_rolled_back(t.id_provider):
raise WalletError.already_rolled_back()
if t.type == "bet" and self.balance().compare(t.amount) < 0:
raise WalletError.insufficient_funds(self.balance())
results.append(BetWinTransactionResult(t.id_provider, self.apply(t)))
return BetWinResult(self.round_id(request.round_id), self.balance(), results)
# … balance(), rollback(), finish()
server = WalletServer(Wallet(), os.environ["BEEXAR_API_SECRET"])
Mount it:
from beexar.asgi import fastapi_router
app.include_router(fastapi_router(server, prefix="/wallet")) # FastAPI
from beexar.asgi import flask_blueprint
app.register_blueprint(flask_blueprint(server, url_prefix="/wallet")) # Flask
from beexar.asgi import asgi_app
application = asgi_app(server) # bare ASGI
Then point the four callback URLs in the backoffice at
https://your-host/wallet/{balance,betwin,rollback,finish} and run the
Integration Test Game — 29 scenarios
against your implementation.
A complete, correct wallet you can read in one sitting:
examples/inmemory_wallet.py. A runnable
server: examples/server.py.
The raw body — read this one
The signature is an HMAC over the exact bytes of the request. If anything parses the JSON and serialises it again before the SDK sees it, those bytes are gone — key order, escaping and number rendering all change — and no signature can ever match again.
| Framework | The trap | What the binding does |
|---|---|---|
| FastAPI | declaring a Pydantic model makes Starlette parse the body and you never see bytes | declares Request, calls await request.body() |
| Flask | request.get_data(as_text=True) re-decodes the body |
request.get_data(cache=True) → bytes |
| ASGI | building the body by concatenating decoded chunks corrupts multi-byte characters | concatenates bytes |
dispatch() refuses a str outright — a decode is not always reversible, and
verifying a lossy copy is worse than failing. When a signature fails and the SDK
can tell why, the on_warning callback receives the reason in plain words.
Money
Amounts and balances are decimal strings in the currency's main unit — "0.90"
is ninety cents. Money cannot be built from a float, raises on float(), and
refuses + and - so an accidental mix with a number is impossible.
str(Money.parse("100.00").sub(Money.parse("0.30"))) # "99.70"
str(Money.from_minor_units(9970, 2)) # "99.70" from an integer ledger
"1E2000000000" and anything past 16 decimals is rejected on shape, before any
arithmetic touches it.
Errors
Two HTTP statuses exist on this contract, 400 and 500, and the meaning lives in
meta.api_code. Raise a WalletError and the envelope is built for you:
raise WalletError.insufficient_funds(current_balance) # 400 / api_code 100
raise WalletError.already_rolled_back() # 400 / api_code 409
raise WalletError.invalid_player() # 400 / api_code 101
Codes 100, 105 and 106 must carry the player's balance, so those constructors take it as a required argument — there is no way to build one without it. Anything else you raise becomes an opaque 500 and your message never leaves the process.
The boundary
The SDK does not do idempotency or tombstones for you, and it will not pretend to. Both have to happen in the same database transaction as the balance update, and no library can join your transaction. What you must do:
- Store every
id_providerwith a unique index, and check it inside the transaction that moves the money. - Store the response you returned — a repeat must return the id and balance you gave the first time, not today's.
- On rollback, record a tombstone for
original_id_providerwhether or not the original exists. Out-of-order delivery is normal; a later/betwinfor a tombstoned id must be refused withWalletError.already_rolled_back().
Your handler has 15 seconds. The platform retries 5xx and timeouts for up to
30 seconds with the same id_provider; it does not retry insufficient funds,
bet limits, bad requests or signature failures.
Types come from the spec
The dataclasses track the published OpenAPI documents (openapi/ in this repo).
tests/test_contract.py reads a normalised description of those specs and fails
if a property the contract declares never reaches your handler.
License
MIT
Metadata
Release files for beexar 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| beexar-1.0.2.tar.gz | 36.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| beexar-1.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.7 kB
Release files / beexar-1.0.2.tar.gz
| Download URL | beexar-1.0.2.tar.gz |
|---|---|
| Size | 36.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f50872b9b20250c030d618d9cd4bf7e615191e98c51cf0bedccba7a225b30f2
|
|
BLAKE2b-256 checksum How to use checksums |
3307e54d4556d36fd90ef988b9d41c01ce8bd3c99416d26c77b7930deb5f5a23
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency logRelease files / beexar-1.0.2-py3-none-any.whl
| Download URL | beexar-1.0.2-py3-none-any.whl |
|---|---|
| Size | 21.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
521da3c6e298e8f0b879038fa8f1d66b0485b1d2a4e54b9f7ea01d91d19408bb
|
|
BLAKE2b-256 checksum How to use checksums |
a7511be2b95b4745df5f7422ced618b69add497a1f5b287787801d175afac1b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency log