Scheduled Jobs & Automation
A gambling platform is not only what happens when a player clicks. A significant amount of correctness lives in background work: recovering half-finished rounds, chasing lost webhooks, expiring bonuses, auditing the books.
Operator control
Jobs run in a dedicated worker process, and operators control them from the backoffice without a developer:
- See every job, grouped into category tabs (money, gaming, sports, promotions, affiliates, media, AI agents, system), with its schedule and run history
- Read the logs of any individual run
- Trigger a run on demand, or retry a failed one
- Pause and resume a job
- Override its schedule at runtime
A job that fails does so visibly, with a failed run record rather than silence. The backoffice Home page also flags every job whose most recent run failed.
What runs, and why it matters
Money integrity
| Job | Frequency | Why it exists |
|---|---|---|
| Ledger integrity | 15 min | Sums every account per currency and fails if any currency does not sum to zero. Also flags unexplained negative balances and unknown account types |
| Checkpoint drift | Nightly | Recomputes every balance from the first ledger entry and compares it with the checkpoint-based balance. A mismatch means a checkpoint is wrong |
| Checkpoint balances | Hourly | Snapshots every balance, so a balance read only adds up the ledger lines since the last checkpoint instead of the account's whole history |
| Segregated funds coverage | Daily | Per currency, checks that segregated assets cover what the operator owes players |
| Round reconciliation | 5 min | Finds half-settled casino rounds and asks the provider what happened |
| Payment reconciliation | Every minute | Resolves deposits and payouts stranded by a lost webhook, and dispatches payouts once their hold window ends. Without it, a dropped webhook leaves a player's deposit pending forever |
| Orphan bet voiding | Hourly | Refunds bets on events settlement can no longer resolve |
| Game-provider commission | Hourly, plus a nightly reconcile | Books what each game provider is owed on GGR, then cross-checks the payable against the accruals |
Bonus and rewards lifecycle
| Job | Frequency | What it does |
|---|---|---|
| Finish expired promotions | 5 min | Moves active and paused promotions past their end date to finished |
| Expire bonuses | Hourly | Forfeits grants past their expiry |
| Auto-convert | 5 min | Converts grants whose wagering has cleared into cash |
| Release locked bonuses | 2 min | Pays the release earned by play into cash for every locked bonus with enough pending |
| Activate queued | 5 min | Promotes the next queued grant when a player's slot frees up |
| Reap stranded | Hourly | Cleans up bonus balances with no live grant behind them |
| Expire free bets and bet boosts | Hourly | Marks unused tokens past their expiry |
| VIP reloads | Every minute | Turns each due VIP reload claim into a held cash reward the player can claim for 24 hours, and retries a failed one |
| Gamification events | Every minute | Checks player activity against active triggers and delivers the rewards. Also finishes any delivery that was interrupted |
Cohorts and email campaigns
| Job | Frequency | What it does |
|---|---|---|
| Cohort bonus grants | Every minute | Grants each queued cohort action's reward to its players one at a time, and picks up an action whose worker stopped mid-run. A restart never pays a player twice |
| Email campaigns | Every minute | Launches campaigns that are due (freezing the audience and running any gift), mails each queued player once the gift reached them, and marks a campaign sent when nothing is left |
Sportsbook and catalog
- Game catalog sync every 6 hours. If any provider fails, the run is marked failed and its reason names each failing provider and its error, so the Jobs screen shows which one to look at.
- Every five minutes, each casino game's distinct players over the last seven days are recomputed, which the lobby shelves are ordered by.
- Daily sweeps that backfill team crests, league badges and competitor nationalities from artwork sources. These run off the hot path because artwork changes rarely and the sources are rate-limited.
- Every two minutes, feed messages that arrived before their fixture existed are replayed onto the fixture.
- Every minute, new events are linked to their competitors.
- Every five minutes, sports activity is rolled up for the Trending ranking.
- The ranking scores are recomputed (the fast "hot" ranking every five minutes, the popularity rankings every 15), and an hourly job moves each region's mix toward its targets and ends ranking levers past their expiry. See Rankings & Discovery.
- Every ten minutes, and the fast-moving sections every minute, the lobby's page sections are pre-built so a request rarely builds one.
- Every 15 minutes, each recently active player's sport, competition and market affinities are rebuilt for the personalised lobby sections.
- Hourly, new competitors are matched to Wikidata for country flags (the language model tie-break runs only when an Anthropic key is configured).
- Daily, the recommended market set per provider and sport is written into the market catalog; it never overrules a staff edit.
- Hourly, LSports events still open 12 hours after kickoff with no open bets are ended.
Affiliates
First-deposit and deposit sweeps every five minutes, hourly commission accrual, a nightly bill run that issues each affiliate's bill for the month that just closed (bills wait for approval), a daily automatic withdrawal for affiliates who chose a payout day, a nightly payable reconciliation, postback delivery with retry, nightly clearing of partner tracking parameters past the retention window, and the partner statistics CSV exports (built on request, deleted after 30 days).
Housekeeping
These jobs keep the database from growing without bound:
- Lapsed casino game sessions are marked closed every minute (bookkeeping only: a lapsed session is already unusable the moment it lapses).
- Expired player and staff sessions are removed hourly, and expired email, password-reset and passkey tokens nightly.
- Processed gamification events are pruned after 90 days, keeping the ones whose reference stops a reward firing twice.
- Job run history is pruned nightly to 30 days. Each job's latest finished run is always kept.
- Odds price history is pruned nightly. Prices that closed more than seven days ago are deleted, except those a bet depends on for settlement.
- Raw sports telemetry is pruned after 30 days, and the hourly rollup is kept. Parked feed messages that were applied or discarded are pruned after 30 days, and the viewer-country observations behind the lobby after 90.
- On development and staging installs, finished simulator events and expired simulated player sessions are cleaned up (every 15 minutes and hourly).
- Identity documents are destroyed once their retention window passes. This job only reports what it would destroy until an operator arms it.
Analytics copy (optional)
When the optional ClickHouse analytics store is configured, jobs copy new ledger lines, sports bets, casino rounds, sports activity and the affiliate report data into it every minute. A nightly job compares the last week of daily ledger totals with the main database and re-copies any day that differs. Without ClickHouse, these jobs skip. See Architecture & Deployment.
AI agents
The two scheduled AI agents run as jobs: an hourly ops watchdog and a three-hourly sports catalog check. They get the same pause, trigger and reschedule controls as every other job. While the AI engine's switch is off or its spend cap is reached, they skip without calling the model.
Email and generated art
Transactional mail is queued rather than sent inline, with retries, backoff and a fallback mail provider. A slow mail provider therefore never blocks a player's registration. Artwork generation requested from the backoffice is queued to the worker as well.
Exchange rates
Rates are not fetched by a scheduled job. A refresher inside the worker is the only place rates are fetched: crypto every 30 seconds and fiat every 10 minutes. Every other part of the platform reads what it publishes, and refuses to convert when the rates are too old rather than guess. The backoffice Home page fails its live check when rates stop updating.
Feed ingestion
The live sports feed runs in its own long-lived process, separate from both the API and the worker. A feed problem therefore cannot take the site down, and site load cannot slow ingestion. The same process hosts the LSports live feed and the polling odds feed used for demonstration catalogs. Each can be switched on separately, and both are off by default: with neither enabled the process starts, says there is nothing to run and exits.
When the analytics store is configured, the LSports connection also records every message it receives, plus the fixture snapshots it seeds from, for 120 days, so a match can later be replayed as a demonstration feed. Recording never blocks or fails ingestion.
Settlement of finished events is the feed's job. Events it cannot settle land in the Manual Resolve queue in the backoffice, and the hourly orphan-bet job refunds bets on events that can no longer be settled.
Scope & Roadmap
Implemented. About seventy registered jobs covering the above, plus the scheduled AI agents. A dedicated worker process. Per-run records with logs. Backoffice control over triggering, retrying, pausing, resuming and rescheduling, grouped by category. Nightly pruning of the tables that grow with play. Queued transactional email with retry and fallback. A separate ingester process.
Partial. BetConstruct round reconciliation is not a scheduled job. It exists only as a manual command that reports stuck rounds without moving money, because that vendor exposes no endpoint to query a round's status. Prematch odds sync and score-based settlement are no longer jobs: the live ingester's feeds do that work.
Two convenience commands in the build file are broken: their underlying scripts were removed when those jobs moved to the queue.
Not built. Alerts fire when a job fails to register its schedule at startup, and when an analytics sync goes 15 minutes without a success. There is no general alert for any job that silently stops running. There is also no dead-letter review surface for messages that exhaust their retries.