Add a new external game-provider integration¶
Goal: integrate a licensed external slots / live-casino provider (analogous to PM8, ST8, BGaming, Evogames). Audience: customer engineering team adding a third-party game provider to expand the catalogue. Time: 1–3 weeks. Friction: high — wallet RPC crosses services through Redis pub/sub, which breaks OTel trace propagation. See
docs/audits/perf-trace-coverage-audit.md§"transport gap".
What you'll change¶
| Layer | Path | Action |
|---|---|---|
| Provider module | apps/api/src/casino/slots/providers/<provider>/ |
New folder. Copy from pm8/ or bgaming/. |
| Module wiring | apps/api/src/casino/slots/slot-games.module.ts (and parent) |
Add new module to imports. |
| Auth subsystem | <provider>/system/operator-auth/ |
HMAC, JWT, or API-key per provider docs. |
| Signature subsystem | <provider>/system/signature/ |
Webhook signature verification. |
| Wallet RPC | <provider>/wallet/ |
Cross-service balance / debit / credit / cancel. |
| Launch service | <provider>/<provider>-launch.service.ts |
Game session creation. |
| Webhook controller | <provider>/<provider>.controller.ts |
Provider-side callbacks. |
| Doppler / env | .example.env + Doppler |
Provider URLs, secrets, signature keys. |
| Compliance flag | (see notes) | Whitelist provider per jurisdiction. |
Canonical examples¶
The current providers each illustrate a different auth scheme:
apps/api/src/casino/slots/providers/pm8/— operator-signed HMAC, custom signature module.apps/api/src/casino/slots/providers/bgaming/— different signature pattern.apps/api/src/casino/slots/providers/evogames/— yet another.apps/api/src/casino/slots/providers/st8/— has the most complete admin / bonus / hydrate surface.
Inspect 2–3 of these before picking the closest match to your provider's spec.
The PM8 module structure (verified at apps/api/src/casino/slots/providers/pm8/pm8.module.ts:1-15):
pm8/
├── pm8.module.ts
├── pm8.controller.ts
├── pm8-launch.service.ts
├── api/ # outbound API client
│ └── pm8-api.module.ts
├── system/
│ ├── operator-auth/ # operator → ebit handshake
│ │ └── pm8-auth.module.ts
│ └── signature/ # webhook signature verify
│ └── pm8-signature.module.ts
├── wallet/ # debit / credit / balance / cancel
│ └── wallet.module.ts
└── dto/
Steps¶
1. Provider intake [non-engineering]¶
Before writing code:
- [ ] Provider tech contact secured. NDA / integration agreement signed.
- [ ] Provider sandbox credentials issued (separate from production).
- [ ] Auth scheme documented: HMAC (which hash, which canonicalisation), JWT (signing key, claim shape), API key (where in headers).
- [ ] Webhook spec: which events fire (bet, win, refund, freespin), with what payload and what signature.
- [ ] Wallet RPC spec: what balance / debit / credit / cancel endpoints they call on us.
- [ ] Currency support matrix per provider (mapping to our
CurrencySymbolenum). - [ ] Jurisdictional whitelist: which markets is the provider licensed for? Coordinate with
docs/security-register.mdgating.
2. Add the env keys¶
Edit .example.env:
YOURPROVIDER_API_URL="https://sandbox.yourprovider.example/v1"
YOURPROVIDER_OPERATOR_ID=""
YOURPROVIDER_API_KEY=""
YOURPROVIDER_SIGNING_SECRET=""
# If JWT-based:
YOURPROVIDER_JWT_PUBLIC_KEY=""
Set values in Doppler for each environment.
3. Scaffold the provider module¶
cd ebit-api
cp -r apps/api/src/casino/slots/providers/pm8 \
apps/api/src/casino/slots/providers/yourprovider
# rename: yourprovider.module.ts, yourprovider.controller.ts, yourprovider-launch.service.ts
# rename subfolders: yourprovider-auth.module.ts, yourprovider-signature.module.ts
Inside the new folder, replace every Pm8 with YourProvider. Keep the file structure — sister modules import via the same path conventions.
4. Implement signature verification¶
Open <provider>/system/signature/<provider>-signature.module.ts. The pattern:
- Receive raw request body + signature header.
- Re-compute the signature with the shared secret.
- Constant-time compare with
crypto.timingSafeEqual. - Reject (
401) on mismatch — never echo the expected signature in the error.
Test both positive (valid signature accepted) and negative (mutation of body rejected) cases.
5. Implement wallet RPC [high friction]¶
The wallet handlers are the most-called endpoints in any external-provider integration — the provider hits them on every spin. They live under <provider>/wallet/.
Required handlers (names vary per provider):
balance— return the user's balance for a given currency. Read-only.debit/buyin— deduct the bet amount, write aBetrow.credit/payout— credit a win, update the bet's settlement.cancel— refund a previously debited bet (timeout, error). Must be idempotent.
Pattern verified in ST8 (the most mature integration) at apps/api/src/casino/slots/providers/st8/:
- Uses
@bebkovan/server-core'sWaitMutexto serialise per-user-per-game wallet ops. - Uses Prisma upsert with a unique transaction id to enforce idempotency.
- Handles "cancel of an unknown transaction" by inserting a marker row (
St8CanceledUnknownSlotTransaction) so a late-arriving original debit can be no-op'd.
These idempotency patterns are non-negotiable — providers retry on timeout, and a non-idempotent debit will double-charge users.
6. Wire the module¶
Add YourProviderModule to apps/api/src/casino/slots/slot-games.module.ts imports (or to the parent slots module that aggregates providers).
7. Tracing gap workaround [high friction]¶
The wallet RPC layer routes through ExternalControllerClient over Redis pub/sub. OTel trace context does not propagate — see project_otel_microservice_transport_gap.md and docs/engineering/observability-runbook.md §5.
Mitigations during integration:
- Log the provider's transaction id in every wallet handler so you can correlate by id, not by trace_id.
- Search Loki by user_id, not by trace_id, when chasing a wallet bug:
- Manual span wrapping the wallet handler entry point will at least capture the local processing time:
Plan for this when estimating debugging time.
8. Frontend launch flow¶
The frontend doesn't usually need provider-specific code — the launch URL is a redirect / iframe that the backend builds. Verify the <provider>-launch.service.ts returns a launch URL that the FE can iframe.
9. Compliance / jurisdiction whitelist [non-engineering]¶
Coordinate with the security / compliance team:
- [ ] Provider is licensed for each jurisdiction the customer operates in.
- [ ] Add the provider to the jurisdiction-allowlist (location:
{{TBD: confirm with security review — likely a config in libs/_prisma/src/seed/ or a runtime feature flag}}). - [ ] Update
docs/security-register.mdif the integration introduces new data-sharing flows (bet history outbound, KYC outbound, etc.).
10. Tests¶
Required specs:
1. Signature verify (positive + negative).
2. Wallet idempotency: replay the same debit twice, expect a single bet row.
3. Cancel-of-unknown-transaction handling.
4. Currency mapping (provider currency code → our CurrencySymbol).
11. Sandbox integration¶
Use the provider's sandbox to drive a real round end-to-end:
- Configure the provider's sandbox with the local
ebit-apiURL via ngrok / cloudflared. - Trigger a launch from the FE (or directly via
<provider>-launch.service). - Watch the wallet handlers receive
debit→credit(orcancel). - Confirm DB rows + balance update.
- Repeat with retries (network drops) to exercise idempotency.
12. Update the API surface SOT¶
Append docs/api/changelog.md entry: new endpoints under "Casino API" / "Webhooks" tag.
Verification¶
- Sandbox round trip: Provider → wallet → DB → balance update completes.
- Idempotency: Replay the same
debitrequest 3 times, expect 1Betrow. - Cancel: Provider's cancel arrives, the previously debited amount is restored.
- Loki: search by
provider_transaction_idreturns the full handler chain even though the trace is fragmented. - Postman: new endpoints visible under the regenerated collection.
Cross-links¶
integration-cookbook.md— index.add-payment-provider.md— sister recipe; payment providers share the module-shape pattern.docs/audits/perf-trace-coverage-audit.md— transport gap details.docs/engineering/observability-runbook.md§5 — debugging without trace propagation.docs/data-model/—Bet,SlotTransaction, provider-specific tables.docs/security-register.md— security review for new outbound integrations.- Existing providers:
apps/api/src/casino/slots/providers/{pm8,bgaming,evogames,st8}/.