Enterprise-grade global sportsbook with event-sourced bet log, multi-jurisdiction routing, KYC/AML compliance gate, immutable S3 audit trail, and hourly reconciliation. Designed for regulated markets across US, EU, and UK.
The global compliant architecture for live sports betting exists because gambling is one of the most heavily regulated industries in the world, and operating across multiple jurisdictions adds extraordinary complexity. Each jurisdiction — US states (NJ, PA, MI, CO, etc.), UK (Gambling Commission), EU member states (varying regulations per country) — has its own licensing requirements, responsible gambling mandates, reporting obligations, and audit expectations. A sportsbook that operates globally must comply with all of them simultaneously.
The V1 stream processing variant provides excellent throughput and real-time odds delivery, but it lacks three critical capabilities for global compliance: (1) jurisdiction-aware routing that ensures each bet is processed according to the rules of the user's registered jurisdiction, (2) pre-bet compliance checks for KYC (Know Your Customer), AML (Anti-Money Laundering), deposit limits, and self-exclusion, and (3) an immutable, tamper-proof audit trail that can survive regulatory inspection.
Event sourcing is the foundation of the compliance architecture. Every bet, settlement, compliance decision, and balance change is an immutable event appended to a log — never updated, never deleted. This creates a complete, tamper-proof history that satisfies regulatory requirements across all jurisdictions. If the UK Gambling Commission audits the platform, every bet placed by UK users can be reconstructed from the event log: what odds were offered, what compliance checks were performed, when the bet was placed, how it was settled, and when the payout was processed.
The jurisdiction router is a critical component that does not exist in simpler architectures. When a user places a bet, the router inspects their registered jurisdiction (determined during KYC onboarding) and directs the bet to the appropriate acceptance pipeline. US users in New Jersey follow NJ Gaming Commission rules. UK users follow Gambling Commission rules. EU users follow their country's specific regulations. This routing happens before the KYC/AML gate, which applies jurisdiction-specific compliance rules (different deposit limits per jurisdiction, different self-exclusion databases, different AML thresholds).
The reconciliation service is the platform's financial safety net. It runs hourly, comparing the event log (source of truth) against the balance database (derived state) and payment provider records (external verification). Any discrepancy — a missing settlement, a double-credit, balance drift — is flagged for investigation. In a regulated industry where financial accuracy is legally required, catching discrepancies within an hour limits the blast radius of any bug or failure.
This architecture is relevant in interviews at DraftKings, FanDuel, Flutter Entertainment (Betfair/Paddy Power), and Sportradar. Interviewers expect candidates to discuss event sourcing for auditability, jurisdiction-aware routing for regulatory compliance, KYC/AML as a cross-cutting concern, and the trade-off between compliance overhead (additional latency per bet) and regulatory necessity. The comparison with simpler variants highlights that the added complexity is not over-engineering — it is a legal requirement for operating in regulated gambling markets.
The global compliant architecture uses eleven components organized into four logical layers: edge routing, compliance, bet processing, and audit/reconciliation.
The edge routing layer handles traffic ingestion and initial routing. BettorClient connects through RegionalLB (geo-aware Application Load Balancer) to OddsGateway (API Gateway with JWT auth, rate limiting, and path-based routing). Odds requests are served from the existing odds infrastructure (not fully modeled here to focus on the compliance pipeline). Bet placement requests are routed to the JurisdictionRouter.
The compliance layer is the key differentiator from the V1 architecture. JurisdictionRouter inspects the user's registered jurisdiction from their KYC profile and routes the bet to the appropriate acceptance path. It adds jurisdiction metadata (jurisdiction code, applicable regulations, limit thresholds) to the request. KycAmlGate performs four compliance checks on every bet: (1) KYC status — is the user's identity verified? (2) Self-exclusion — has the user opted out of gambling? (3) Deposit limits — does this bet exceed daily/weekly/monthly limits? (4) AML screening — are there suspicious patterns (unusual bet sizes, rapid deposits, structuring)? Each check is a sub-5ms Redis lookup against pre-loaded compliance data. The gate adds 10-30ms to every bet but is legally required in all regulated markets.
The bet processing layer is similar to V1 but enhanced with compliance context. BetAcceptanceService receives compliance-cleared bets, validates odds against the Redis cache, debits balances in BalanceDatabase (PostgreSQL), writes the bet as an immutable event to EventStore (DynamoDB), and publishes to BetStream (Kafka). Every event includes full regulatory context: jurisdiction, compliance decision ID, KYC status, applicable regulations. This context is essential for regulatory reporting and audit.
The audit/reconciliation layer provides financial integrity assurance. SettlementService consumes bet events from Kafka and processes settlements identically to V1 (batch per event, idempotent). ReconciliationService runs hourly: it reads the event log from EventStore, sums debits and credits per user, compares against BalanceDatabase balances, and verifies against payment provider records. Discrepancies are flagged and written to AuditBucket (S3 with Object Lock). AuditBucket receives a copy of every bet event and settlement event as immutable JSON objects — these cannot be modified or deleted and serve as the legal record for regulatory inspection.
The S3 audit trail uses Object Lock in compliance mode, which means objects cannot be deleted even by AWS root account until the retention period expires (typically 7 years for gambling records). This provides stronger immutability guarantees than the DynamoDB event store, which can theoretically be modified by administrators. The S3 trail is the system's legal backstop — the evidence that would be presented in a regulatory hearing or billing dispute.
Scaling follows the same patterns as V1 with additional considerations for the compliance layer. JurisdictionRouter is CPU-light (5ms per decision) and scales easily to 160K RPS. KycAmlGate is IO-bound (4 Redis lookups per bet) and scales based on bet volume. BetAcceptanceService scales identically to V1's BetService. ReconciliationService scales based on event volume per hour — larger time windows require more workers.
The compliance pipeline adds two layers between the API Gateway and the Bet Acceptance Service: the Jurisdiction Router and the KYC/AML Gate. Every bet passes through both layers before reaching the acceptance service. The Jurisdiction Router is fast (~5ms) — it reads the user's jurisdiction from their profile and adds metadata. The KYC/AML Gate is the latency-dominant step (~10-30ms) — it performs four Redis lookups to verify identity, check self-exclusion, validate deposit limits, and screen for AML flags.
The key design insight is that these compliance checks are mandatory and synchronous — they must complete before the bet is accepted. This is a regulatory requirement, not an optimization choice. The alternative (accepting the bet and checking compliance asynchronously) would allow self-excluded users to gamble and users to exceed deposit limits, which are serious regulatory violations.
Step-by-Step Walkthrough
Pseudocode
// JURISDICTION ROUTING (~5ms)
async function routeBet(user_id, bet_request):
// Lookup user jurisdiction from KYC profile
profile = await redis.get("profile:" + user_id)
jurisdiction = profile.jurisdiction // "US-NJ", "UK", "EU-DE"
// Add jurisdiction metadata for compliance pipeline
bet_request.jurisdiction = jurisdiction
bet_request.deposit_limit_threshold = JURISDICTION_LIMITS[jurisdiction]
bet_request.applicable_regulations = JURISDICTION_RULES[jurisdiction]
return routeToKycGate(bet_request)
// KYC/AML COMPLIANCE GATE (~10-30ms)
async function checkCompliance(user_id, jurisdiction, stake):
// 4 parallel Redis lookups (each ~5ms)
[kyc, exclusion, limits, aml] = await Promise.all([
redis.get("kyc:" + user_id), // Identity verified?
redis.get("exclusion:" + user_id), // Self-excluded?
redis.get("limits:" + user_id), // Within deposit limits?
redis.get("aml:" + user_id), // AML flags?
])
if kyc.status != "verified": return reject("KYC_INCOMPLETE")
if exclusion.excluded: return reject("SELF_EXCLUDED")
if limits.daily_remaining < stake: return reject("LIMIT_EXCEEDED")
if aml.flagged: return reject("AML_FLAG")
// Log compliance decision as an event (for audit)
decision_id = await eventStore.append({
type: "COMPLIANCE_DECISION",
user_id, jurisdiction,
checks: { kyc: "pass", exclusion: "pass", limits: "pass", aml: "pass" },
overall: "approved"
})
return { approved: true, decision_id }
// BET ACCEPTANCE (with compliance context)
async function acceptBet(user_id, selection, odds, stake, decision_id, jurisdiction):
// Validate odds against Redis cache
current = await redis.get("odds:" + market_id)
if abs(current.odds - odds) > TOLERANCE: return 409
// Debit balance (atomic row-level lock)
result = await db.execute(
"UPDATE account_balances SET balance_cents = balance_cents - $1
WHERE user_id = $2 AND balance_cents >= $1",
[stake * 100, user_id]
)
if result.rowCount == 0: return 400 // Insufficient funds
// Write immutable bet event (includes compliance context)
await eventStore.append({
type: "BET_PLACED",
user_id, selection, odds, stake,
jurisdiction, compliance_decision_id: decision_id,
created_at: now()
})
// Publish to Kafka for settlement
await kafka.produce("bet-events", key=event_id, value=bet_event)
return 201 // ~250ms total including compliance pipelineThe storage architecture has four tiers, each serving a different purpose with different consistency and retention requirements. The DynamoDB Event Store is the source of truth — every bet lifecycle event is immutable. PostgreSQL Balance DB stores derived state (current balances) with ACID guarantees for financial operations. Redis stores ephemeral compliance data (KYC status, exclusion lists, limits) for fast pre-bet checks. S3 with Object Lock stores the immutable legal record that survives even if all other systems are compromised.
The reconciliation flow connects three of these tiers: Event Store (what happened) vs Balance DB (current state) vs S3 (audit record). Three-way agreement provides the highest confidence in financial accuracy.
Step-by-Step Walkthrough
Pseudocode
// TIER 1: DynamoDB Event Store (immutable source of truth)
await dynamodb.put({
partition_key: "BET_PLACED#2026-05-27", // Temporal partitioning
sort_key: "evt_001_snowflake_id",
user_id: "usr_456",
sporting_event_id: "match_123",
jurisdiction: "US-NJ",
compliance_decision_id: "cd_789",
odds: 2.10, stake: 50.00,
status: "PLACED",
created_at: "2026-05-27T15:30:00Z"
})
// NEVER: dynamodb.update() or dynamodb.delete() on this table
// TIER 2: PostgreSQL Balance DB (derived state, ACID)
// Atomic debit with balance check (prevents overdraft)
UPDATE account_balances
SET balance_cents = balance_cents - 5000,
weekly_stake_total_cents = weekly_stake_total_cents + 5000
WHERE user_id = 'usr_456'
AND balance_cents >= 5000; -- Atomic check-and-debit
// TIER 3: Redis Compliance Data (ephemeral, refreshed every 60s)
SET "kyc:usr_456" '{"status":"verified","verified_at":"2026-01-15"}'
SET "exclusion:usr_456" '{"excluded":false}'
SET "limits:usr_456" '{"daily_remaining_cents":100000}'
SET "aml:usr_456" '{"flagged":false}'
// TIER 4: S3 Audit Trail (immutable, 7-year retention)
s3.putObject({
bucket: "betting-audit-trail",
key: "2026/05/27/US-NJ/BET_PLACED/evt_001.json",
body: JSON.stringify(bet_event),
objectLockMode: "COMPLIANCE",
objectLockRetainUntilDate: "2033-05-27" // 7-year retention
})
// RECONCILIATION: Compare all three tiers (hourly)
async function reconcileHour(hour):
events = dynamodb.query("BET_PLACED#" + hour.date)
balances = postgres.query("SELECT SUM(balance_cents) GROUP BY jurisdiction")
audit = s3.listObjects("" + hour.date + "/")
discrepancies = compare(events, balances, audit)
if discrepancies.length > 0:
alert("RECONCILIATION_DISCREPANCY", discrepancies)
await s3.putObject("reconciliation-reports/...", discrepancies)Choice
DynamoDB with INSERT-only writes for all bet lifecycle events
Rationale
Gambling regulators require a complete, tamper-proof audit trail. The append-only pattern (never UPDATE, never DELETE) creates this trail inherently. Every state change — bet placement, compliance decision, settlement, balance adjustment — is a separate immutable event. If a regulator asks 'show me every action taken on this user's account', the event store provides a complete, ordered, unmodifiable history. This is not optional — operating without an audit trail in regulated markets is illegal.
Choice
Dedicated service routing bets to jurisdiction-appropriate pipelines
Rationale
Different jurisdictions have fundamentally different requirements. US states: per-state licensing with different bet types allowed (NJ allows parlays, some states restrict prop bets). UK: Gambling Commission compliance with specific responsible gambling interventions (reality checks, session time limits). EU: varying by country. Embedding all rules in one service creates a monolith that changes every time any jurisdiction updates its regulations. The router pattern allows per-jurisdiction logic to be deployed and scaled independently.
Choice
Separate compliance service checking identity, limits, and AML before bet acceptance
Rationale
KYC/AML compliance is a cross-cutting concern that applies to every bet in every jurisdiction. Separating it from bet acceptance enables: independent scaling (compliance checks are IO-bound, not CPU-bound), independent deployment (AML rule updates without redeploying the bet service), clear audit boundary (the gate produces compliance decision events that are part of the audit trail), and independent testing (compliance rules can be verified in isolation).
Choice
S3 Object Lock (compliance mode) for regulatory records
Rationale
S3 Object Lock in compliance mode provides physical immutability — once written, objects cannot be deleted or modified by anyone, including AWS administrators, until the retention period expires. This is stronger than DynamoDB's logical immutability (the application uses INSERT-only, but an admin could theoretically DELETE rows). The S3 audit trail is the legal evidence for regulatory inspections. 7-year retention satisfies most gambling commission requirements.
Choice
Batch job comparing event log vs balance DB vs payment provider
Rationale
In a distributed system with multiple write paths, discrepancies can accumulate from partial failures, race conditions, or bugs. Hourly reconciliation limits the blast radius: a settlement bug that causes incorrect payouts is caught within an hour, not at the end of the month. The reconciliation compares three independent sources: the event log (source of truth), the balance database (derived state), and the payment provider (external verification). Three-way agreement provides high confidence in financial accuracy.
Choice
INSERT ON CONFLICT (event_sequence_number) DO NOTHING
Rationale
Kafka delivers at-least-once: the same settlement event may be processed twice after a consumer rebalance or crash recovery. Without idempotency, a retry double-pays a winning bet — a financial loss and regulatory violation. Event sequence numbers provide a natural deduplication key: each event has a unique, monotonic sequence number, and the settlement INSERT checks for conflict. Processing the same event twice has no effect on the balance.
Target RPS
350K peak (200K odds + 100K bets + 50K misc)
Latency (p99)
<100ms odds, <250ms bet (includes KYC/AML)
Storage
~2 TB/year (event log + S3 audit + balance DB)
Availability
99.99% (multi-region, event-sourced, S3 Object Lock)
| Operation | Time | Space | Notes |
|---|---|---|---|
| Bet placement (full compliance pipeline) | O(1) — routing (5ms) + 4 Redis lookups (20ms) + odds validation (2ms) + DB write (30ms) + Kafka produce (5ms) | O(1) — one event per bet in DynamoDB + Kafka | Total ~62ms CPU path, ~250ms p99 end-to-end including network hops. KYC/AML gate is the dominant latency contributor at 10-30ms. |
| Hourly reconciliation | O(E) — where E is events in the hour | O(U) — where U is unique users with activity | At 100K events/hour: reconciliation takes ~5 minutes with 4 workers. Reads event log + balance DB + payment provider records. |
| Regulatory audit query (all bets for user over N months) | O(B) — where B is the user's total bets in the period | O(B) — results streamed from DynamoDB user_id partition | DynamoDB user_id GSI enables efficient per-user audit queries. S3 audit trail provides secondary verification. |
| Settlement (batch per event) | O(B) — where B is open bets for the sporting event | O(B) — loads all bets for the event into memory | Identical to V1 settlement. Idempotent via event sequence numbers. 500K bets settles in ~30 seconds with 20 workers. |
Immutable event-sourced bet log. Every bet placement, settlement, and compliance decision is an immutable event with full regulatory context. Partition key: event_type#date (temporal partitioning for efficient hourly reconciliation queries). Sort key: event_id (snowflake). Secondary indexes on user_id for per-user audit and sporting_event_id for settlement.
Partition: event_type#date
Indexes: PK on (event_type#date, event_id), GSI on (user_id, created_at) for per-user audit, GSI on (sporting_event_id, event_id) for settlement
Append-only: no UPDATEs or DELETEs ever. Temporal partition key enables efficient hourly reconciliation (query one day's partition instead of full scan). At 100K events/day, each daily partition contains ~100K items.
User account balances with jurisdiction-specific deposit limit tracking. Debited on bet placement, credited on settlement. Row-level locking prevents overdraft from concurrent bets. Rolling deposit/stake totals enable real-time limit enforcement in the KYC/AML gate.
Indexes: PK on user_id, idx_jurisdiction ON (jurisdiction) for regulatory reporting
Balance updates use UPDATE ... SET balance_cents = balance_cents - stake WHERE balance_cents >= stake to prevent overdraft atomically. ReconciliationService reads SUM(balance_cents) GROUP BY jurisdiction for cross-verification.
Immutable JSON objects in S3 with Object Lock (compliance mode). Every bet event and settlement event is written as an individual JSON object, organized by date/jurisdiction/event_type. Objects cannot be modified or deleted during the retention period (7 years). This is the legal record for regulatory inspection — the evidence presented in audits and dispute resolution.
Partition: S3 prefix: date/jurisdiction/event_type
Object Lock compliance mode: no one (including root) can delete objects until retention expires. Storage cost: ~$0.023/GB/month (S3 Standard). At 100K events/day with ~1KB per event: ~36 GB/year. Lifecycle policy moves to Glacier after 1 year for cost optimization.
Settlement command events published when sporting events complete. Consumed by SettlementService to trigger batch settlement. Includes the sporting event ID, outcome, and settlement timestamp. Partitioned by sporting_event_id for parallel settlement across concurrent events.
Partition: sporting_event_id
Published by an external feed integration service (not modeled in this template) when game outcomes are confirmed. Dual-sourced from multiple feed providers for outcome verification.
Published by BetAcceptanceService when a compliance-cleared bet is successfully accepted. Includes full regulatory context: jurisdiction, compliance decision ID, KYC status. Consumed by SettlementService and audit pipeline.
Key Schema
sporting_event_id (string)
Value Schema
{ event_id: string, bet_id: string, user_id: string, sporting_event_id: string, selection_id: string, odds: number, stake: number, jurisdiction: string, compliance_decision_id: string, kyc_status: string }
Published by SettlementService when a bet outcome is determined. Includes payout amount and settlement timestamp. Consumed by notification services, balance update pipeline, and audit trail writer.
Key Schema
sporting_event_id (string)
Value Schema
{ event_id: string, bet_id: string, user_id: string, outcome: "WON"|"LOST"|"VOID", payout_cents: number, settled_at: string, jurisdiction: string }
Published when a sporting event outcome is confirmed. Triggers batch settlement of all open bets for the event. Dual-sourced from multiple feed providers for outcome verification before settlement begins.
Key Schema
sporting_event_id (string)
Value Schema
{ sporting_event_id: string, outcome: object, settlement_time: string, source: string, confidence: number }
Published by KycAmlGate for every compliance check (pass or fail). Consumed by the audit trail writer. Provides a complete record of every compliance decision for regulatory reporting.
Key Schema
user_id (string)
Value Schema
{ decision_id: string, user_id: string, jurisdiction: string, checks: Array<{type: string, result: "pass"|"fail", detail: string}>, overall: "approved"|"rejected", reason_code: string }
Odds change mid-bet while compliance checks are running
Impact
The KYC/AML gate takes 10-30ms, during which odds may change. By the time the bet reaches BetAcceptanceService, the odds in the request may not match the current Redis cache. Rejection rate increases by ~1-3% compared to V1 due to the additional latency.
Mitigation
BetAcceptanceService uses the same odds tolerance threshold as V1 (accept within 5% of current odds). The compliance pipeline is optimized for minimal latency (Redis lookups, not database queries). The 1-3% additional rejection rate is the cost of pre-bet compliance — regulators require it.
Double settlement after Kafka consumer rebalance
Impact
If SettlementService processes a batch, commits offsets, then crashes before the offset commit is persisted, Kafka delivers the events again on rebalance. Without idempotency, users receive double payouts.
Mitigation
Idempotent settlement using event sequence numbers. INSERT ON CONFLICT (event_sequence_number) DO NOTHING. Even if the same settlement events are processed twice, the second processing has no effect on balances or ledger records.
Regulatory audit request (UK Gambling Commission inspects all UK user activity for Q1)
Impact
Large query against both DynamoDB event store and S3 audit trail. The query scans 3 months of events filtered by jurisdiction='UK'. Could return millions of records.
Mitigation
DynamoDB temporal partition key (event_type#date) enables efficient date-range scans without full table scans. S3 audit trail is organized by date/jurisdiction prefix, enabling targeted reads. Both sources should produce identical results — discrepancies indicate a bug in the audit pipeline.
New US state legalizes sports betting (need to add jurisdiction)
Impact
A new jurisdiction (e.g., US-TX) must be added to the system: new routing rules in JurisdictionRouter, new compliance thresholds in KycAmlGate, new acceptance pod configuration in BetAcceptanceService.
Mitigation
Jurisdiction configuration is externalized (not hardcoded). Adding a new jurisdiction requires: (1) update jurisdiction config (routing rules, compliance thresholds), (2) deploy KycAmlGate with new self-exclusion database for the state, (3) test end-to-end with synthetic bets. No changes to the core bet acceptance or settlement logic. Typical time-to-launch for a new US state: 2-4 weeks.
| Component | Failure | Impact | Mitigation |
|---|---|---|---|
| JurisdictionRouter | Routing misconfiguration (wrong jurisdiction applied) | Bets are processed under the wrong jurisdiction's rules. Users may exceed limits or bypass self-exclusion. Regulatory violation. | Jurisdiction configuration is version-controlled and requires dual-approval before deployment. Every routing decision is logged as an event for audit. Reconciliation service includes a jurisdiction-consistency check. |
| KycAmlGate | Redis compliance data stale (self-exclusion list not updated) | A self-excluded user is able to place bets because the exclusion list in Redis was not refreshed. Serious regulatory violation. | Self-exclusion list is refreshed every 60 seconds from the KYC database. A staleness monitor alerts if the list is more than 5 minutes old. Emergency kill switch: set all markets to 'suspended' if the compliance data source is unreachable. |
| S3 Audit Bucket | Write failure (S3 throttling during peak settlement) | Audit records are not written to S3. The DynamoDB event store still has the records, but the S3 audit trail has gaps. Regulatory inspection may find incomplete records. | Audit writer implements retry with exponential backoff. Failed writes are queued in a DLQ (dead letter queue) and retried. ReconciliationService verifies that every DynamoDB event has a corresponding S3 record — gaps trigger alerts. |
| ReconciliationService | Reconciliation job fails mid-way through hourly batch | Discrepancies for the incomplete hour are not detected. Financial accuracy is unverified for that period. | Reconciliation jobs are idempotent — re-running the same hour produces the same results. Failed jobs are automatically retried. The next hour's reconciliation also checks the previous hour (sliding window) to catch any missed discrepancies. |
| BalanceDatabase (PostgreSQL) | Row lock contention on hot accounts | High-volume bettors (placing 10+ bets/minute) experience increased latency due to row-level lock contention on their balance row. Bet placement latency spikes from 250ms to 500ms+. | Implement balance reservation pattern: pre-debit a session balance into Redis, validate bets against the reserved balance, and reconcile with PostgreSQL periodically. This reduces per-bet PostgreSQL writes. |
Horizontal scaling with jurisdiction-level granularity: (1) JurisdictionRouter scales on overall bet volume — auto-scale at 70% CPU. (2) KycAmlGate scales on bet volume — each Redis lookup is ~5ms, so 600 threads handle 120K bets/sec. (3) BetAcceptanceService scales identically to V1 BetService (CPU-based auto-scaling). (4) SettlementService scales on Kafka consumer lag — add workers during heavy settlement periods. (5) ReconciliationService scales on hourly event volume — 4 workers handle 100K events/hour; double for peak periods. Pre-scaling for major events: 2 hours before Super Bowl, scale all compliance-path services to 3x and settlement workers to 5x. Jurisdiction-specific scaling: if a new US state launches and generates spike traffic, only the affected jurisdiction's pods are scaled.
Key metrics: (1) KYC/AML gate pass rate by jurisdiction — alert if any jurisdiction's pass rate drops below 90% (may indicate misconfigured rules) or rises above 99.5% (may indicate bypassed checks). (2) Jurisdiction router latency — should be < 10ms; spikes indicate config loading issues. (3) Reconciliation discrepancy count — alert on any non-zero critical discrepancy. (4) S3 audit write success rate — must be 100%; alert on any failure. (5) Settlement idempotency hits — count of duplicate settlement attempts; non-zero indicates Kafka consumer rebalance or crash recovery. (6) Per-jurisdiction bet volume — monitor for regulatory reporting (some jurisdictions require daily volume reports). (7) Self-exclusion check latency — must be < 5ms; spikes indicate Redis issues. Dashboard: Grafana with jurisdiction-level drill-down showing per-jurisdiction bet volume, compliance pass rate, settlement throughput, and reconciliation status. SLIs: bet acceptance p99 < 250ms (including compliance), settlement p99 < 5 minutes, reconciliation discrepancy detection within 2 hours.
At 100K concurrent users across 3 jurisdictions: RegionalLB + OddsGateway (~$200/month), JurisdictionRouter 8 pods (~$300/month), KycAmlGate 6 pods (~$250/month), BetAcceptanceService 20 pods (~$800/month), DynamoDB EventStore on-demand (~$1,200/month at 100K events/day), MSK Kafka 3-broker (~$600/month), BalanceDatabase PostgreSQL db.r7g.2xlarge (~$700/month), S3 AuditBucket (~$50/month for 36 GB/year + Object Lock), SettlementService 20 workers (~$400/month), ReconciliationService 4 workers (~$150/month), Redis compliance data (~$200/month). Total: ~$4,850/month infrastructure + ~$3,000/month for compliance data providers (self-exclusion databases, AML screening services). Grand total: ~$8,000/month. This is 2.3x the V1 cost, but the compliance infrastructure is legally required — the alternative is not operating in regulated markets.
Authentication: JWT tokens with jurisdiction-scoped claims (user's jurisdiction encoded in the token). Rate limiting: per-user bet rate limiting with jurisdiction-specific thresholds (UK allows higher frequency for horse racing). Anti-fraud: KYC/AML gate screens for suspicious patterns — large bets immediately after account creation, structuring (splitting large bets to avoid reporting thresholds), correlated accounts (multiple accounts from the same household). Responsible gambling: self-exclusion lists checked on every bet (legally required), deposit limits enforced pre-bet, cooling-off periods (24-hour freeze after voluntary limit decrease), reality checks (session duration notifications pushed to client). Data protection: PII encrypted at rest and in transit, jurisdiction-specific data residency (EU user data stays in EU region), GDPR pseudonymization for audit records. Anti-money laundering: AML transaction monitoring integrated with compliance gate, suspicious activity reports (SARs) filed automatically when thresholds are exceeded.
Canary deployment for all compliance-path services (JurisdictionRouter, KycAmlGate, BetAcceptanceService) — route 5% of traffic to the new version, monitor compliance pass rates and latency for 30 minutes, then gradually increase to 100%. Compliance configuration changes (new jurisdiction rules, updated deposit limits) are deployed independently from code changes via a config management system. DynamoDB and S3 require no deployment — they are managed services. Kafka topics are not modified during deployments. Rollback: immediate traffic shift back to the previous version at the ALB level. Jurisdiction-specific deployments: when regulations change in one jurisdiction, only the affected pods are updated — other jurisdictions are not affected.
| Variant | Tier | Latency | Throughput | Cost | Complexity | Reliability |
|---|---|---|---|---|---|---|
| V0: Naive (Polling + Monolith) | T1 | 50-200ms odds, 100-300ms bet | ~2K RPS total | $730/month | Low | 99% (single DB) |
| V1: Event-Sourced Ledger (WebSocket + Kafka) | T2 | <100ms odds, <200ms bet | 350K RPS peak | $3,500/month | Medium | 99.9% (multi-AZ) |
| V3: Global Compliant (Event-Sourced + Jurisdiction Router) | T4 | <100ms odds, <250ms bet | 350K RPS peak | $8,000/month | Very High | 99.99% (multi-region) |
This template is for educational and illustration purposes only. It may not represent the optimal production design for this problem. Real-world systems involve additional considerations (compliance, specific cloud provider constraints, organizational requirements) not captured here. Use this as a starting point for discussion, not as a production blueprint.
Regulatory requirements mandate that compliance checks happen before the bet is accepted, not after. If a self-excluded user places a bet and the system processes it asynchronously, the bet is recorded and potentially settled before the exclusion check completes. This violates responsible gambling regulations and can result in license revocation. The 10-30ms latency overhead is the cost of legal compliance — non-negotiable in regulated markets.
The jurisdiction is determined by the user's registered address during KYC onboarding, not their current location. A user registered in New Jersey can bet while traveling in Pennsylvania, and their bets are processed under NJ rules. However, some jurisdictions (particularly US states) require geo-fencing — the user must be physically present in the state to place a bet. Geo-verification is a separate check in the KYC/AML gate, using IP geolocation and GPS data from the mobile app.
Discrepancies are categorized by severity. Minor discrepancies (< $100, caused by timing differences between event writes and balance updates) are auto-resolved on the next reconciliation run. Major discrepancies (> $100 or affecting more than 10 users) trigger an alert to the finance team and the compliance team. Critical discrepancies (affecting settlement correctness) trigger automatic suspension of settlement for the affected event until manual investigation confirms the correct outcome. All discrepancies are logged to the S3 audit trail.
This is one of the most challenging compliance tensions in the industry. GDPR requires data deletion on user request, but gambling regulations require audit trail retention for 5-7 years. The resolution varies by jurisdiction: UK Gambling Commission guidance says gambling regulatory requirements supersede GDPR deletion rights for wagering records. EU implementations pseudonymize PII (replace user_id with a hashed token) while retaining the bet record. The S3 Object Lock prevents deletion regardless — pseudonymization is the compliance path.
The global compliant architecture is necessary when: (1) operating in more than one jurisdiction (any multi-state US sportsbook or international operator), (2) regulatory inspection is expected (all licensed operators), (3) the sportsbook handles more than $10M/month in wagers (financial accuracy matters at this scale). It is overkill for: (1) single-jurisdiction operators with simple compliance requirements, (2) social betting platforms (not real money), (3) crypto-native betting platforms in unregulated markets. The V1 Stream variant with basic compliance checks is sufficient for most single-jurisdiction operators.
Sign in to join the discussion.
Ready to design your own Live Sports Betting?
Open the simulator, place components on the canvas, wire them up, and run a traffic simulation to see how your architecture performs under real load.
Open Simulator