Wager Up
A real-time skill-based wagering platform for golf, esports, basketball, football, darts, and bowling — with live game state sync, integrated payments, and a modern animated UI.
The Concept
Wager Up is a platform where players compete head-to-head in skill-based competitions with real money on the line. Unlike gambling, every outcome is determined by player skill — you're betting on your own performance.
Players create matches, set wager amounts, and compete across 18 different game types in 6 categories. The platform handles match creation, opponent matching, real-time chat, wallet management, and automatic payout to winners.
Key Features
- 6 game categories: Golf, Video Games, Basketball, Football, Darts, Bowling
- Real-time match updates via WebSocket (Socket.IO)
- Digital wallet with hold/capture/release pattern for wagers
- Stripe Checkout for deposits with webhook verification
- Google OAuth authentication
- In-match chat with message persistence
- Leaderboard with win/loss tracking
- Age verification and terms acceptance gate
Architecture & Technology
React 19
Modern frontend with Tailwind CSS and Framer Motion
FastAPI
Async Python backend with REST API
Socket.IO
Real-time bidirectional communication
Stripe
Deposits, webhook verification, idempotent processing
Google OAuth
Secure authentication with session cookies
AWS ECS Fargate
Containerized deployment with CI/CD
How the Wallet System Works
Hold / Capture / Release
When a player creates or joins a match, the wager amount is placed on hold — deducted from their available balance but not yet spent. This prevents double-spending even under concurrent requests.
When the match completes, holds are captured (money leaves the wallet) and the winner receives the total pot. If a match is cancelled, holds are released and funds return to available balance.
Race Condition Protection
Financial operations use SQLite's BEGIN IMMEDIATE to acquire write locks before balance checks. This prevents two concurrent requests from both passing a balance check before either deducts.
Match joining uses atomic UPDATE statements with WHERE conditions — if two players try to join the same match simultaneously, only one succeeds. The other gets a clean error message.
Security & Reliability
Google OAuth 2.0
Secure authentication with proxy header support behind ALB
Atomic Wallet Operations
BEGIN IMMEDIATE transactions prevent double-spend race conditions
Rate Limiting
Per-endpoint limits on auth, deposits, match creation, and result reporting
Security Headers
HSTS, X-Frame-Options, CSP, Referrer-Policy on all responses
Locked CORS
Origin-restricted CORS on both HTTP and WebSocket connections
Input Validation
Wager amount limits, message length caps, and Pydantic request validation
The Build Journey
Wager Up came together in focused phases — each one shipped something usable.
Auth + Match Creation
Google OAuth, user profiles, and the core match creation flow. Users could create a match, set a wager, and pick a game type. No real money yet.
Wallet + Stripe Deposits
Added the digital wallet with hold/capture/release, Stripe Checkout for deposits, and webhook-driven balance updates. This was the riskiest phase — we spent extra time on concurrency and idempotency.
Real-Time Match Play
Socket.IO integration for live match state, in-match chat, and opponent matching. The frontend transitioned from polling to event-driven updates, cutting latency dramatically.
Game Expansion + Polish
Grew from 2 game types to 18, added leaderboards, age verification, terms gate, and the animated UI using Framer Motion. Full deploy on AWS with CI/CD.
Key Technical Challenges
Preventing double-spend under real concurrency
Two players joining the same match at the same moment could each pass a balance check before either was deducted. We solved this with SQLite's BEGIN IMMEDIATE transactions and atomic UPDATE-WHERE statements. Either one request wins cleanly or it errors out — there's no state where both succeed.
Stripe webhooks that are actually trustworthy
Webhook events arrive late, out of order, and sometimes twice. We verify every webhook signature, store the event ID for idempotency, and only apply state changes once per event. Balance reconciliation pulls from Stripe on read if there's any ambiguity, so wallet balances are always accurate.
WebSockets behind an AWS load balancer
Socket.IO behind ALB is fiddly — sticky sessions, CORS, and proxy headers all have to line up or nothing connects. We locked CORS to known origins, configured ALB for WebSocket upgrades, and made the Python backend honor X-Forwarded-* headers so OAuth redirects still work in production.
Services Behind This Build
Need a Real-Time Platform?
We build interactive, real-time applications with WebSockets, payments, and modern UI. Let's talk about your project.
Get a Free Consultation