Sportsbook integrations and boundaries
The provider adapter contract, sandbox adapters, sandbox webhooks, outbound webhooks, the worker, email, MCP, AI chat, and operations endpoints, and what each one does not prove.
A ProviderConnection record describes an adapter installation an operator or onboarding created. A certificationStatus of SANDBOX, PENDING, CERTIFIED, or SUSPENDED is a stored value. It is not proof that a provider, regulator, or testing lab accepted anything.
Provider adapter contract
features/sportsbook/adapters/contracts.ts defines one ProviderAdapter interface: manifest, capabilities, validateConfig, execute (with an idempotency key), query, verifyWebhook, normalize (to a canonical envelope), health, and reconcile.
Sandbox adapters
features/sportsbook/adapters/sandbox.ts is the only implementation. SandboxAdapter is deterministic and credential-free. It reports production-certified as unsupported, accepts only sandbox: config references, and keeps results in process memory. Kinds: sports, results, kyc, aml, geo, payments, messaging, analytics, tax, reporting, integrity, affiliates, pam. Onboarding creates a sandbox-<kind> connection for each.
Where the code actually calls an adapter:
| Use | Adapter | Notes |
|---|---|---|
| Customer and operator payments | sandbox-payments | executePaymentProvider refuses any other adapter (PAYMENT_ADAPTER_UNSUPPORTED) |
| Customer verification | sandbox-kyc, sandbox-geo | Approves when the entered age meets the configured minimum |
| AML screening | sandbox-aml | Used by recordComplianceScreening |
| Prices | sandbox-sports | ingestSandboxPrice stores a raw envelope and a price snapshot |
| Results | sandbox-results | ingestSandboxResult stores a pending result version |
features/integrations/payment only re-exports the sandbox adapter and the contract types. There is no Stripe, PayPal, or other real payment adapter in this repository.
Sandbox webhooks
/api/sandbox/webhooks/[kind] accepts the thirteen adapter kinds. It returns 404 unless sandbox mode is on, caps the body size, requires an x-sandbox-timestamp within five minutes, verifies an HMAC with SANDBOX_WEBHOOK_SECRET, rate-limits by client key, and records replay protection in WebhookReplay. Accepted messages land in RawEnvelope and InboxMessage for the worker.
Outbound webhooks
WebhookEndpoint records are created through requestWebhookEndpoint and applyWebhookEndpoint (dual control) and tested with testWebhookEndpoint. The worker delivers WebhookDelivery records from the outbox:
- HTTPS only, port 443, no credentials in the URL, and the host must be in
WEBHOOK_EGRESS_HOSTSand resolve to a public address. - Each request is signed with HMAC-SHA256 using
OUTBOUND_WEBHOOK_SECRETand carriesopenfront-delivery-id,openfront-event-type,openfront-event-version,openfront-timestamp,openfront-key-id, andopenfront-signatureheaders. - In sandbox deployment mode, endpoints must be
.localhosts and deliveries are not sent. - Each attempt is stored in
IntegrationDeliveryAttempt; failures retry and then dead-letter.
Worker
npm run worker runs features/sportsbook/workers/main.ts. It writes a WorkerHeartbeat, then loops: drain OutboxMessage, InboxMessage, and ScheduledJob rows with leases, and enqueue audit anchors. Supported outbox events include settlement.requested, recovery.requested, report.generate, reconciliation.run, provider.delivery, ingestion.replay, wager, cash-out, payment, promotion, provider, result, dispute, and export events, and audit.anchor. Anchoring writes signed anchor files to AUDIT_ANCHOR_DIR with AUDIT_ANCHOR_SECRET. Without those two values anchoring is skipped and /api/ready reports it.
Nodemailer with SMTP_HOST, SMTP_PORT (default 587), SMTP_USER, SMTP_PASSWORD, SMTP_FROM, and SMTP_STORE_LINK sends operator password reset and customer recovery emails. Other Notification channels (SMS, push, in-app, webhook) are records; no SMS or push provider is wired.
Storage
The source does not use object storage. No list has an image or file field. The store logo is a stored icon name or sanitized SVG plus a color. Report and export artifacts store a reference string, not an uploaded file.
MCP and AI chat
/api/mcp-transport/[transport]is a JSON-RPC MCP server that forwards the caller's cookie to/api/graphql, so every tool runs with the caller's operator permissions. Tools are the generic schema tools only:listModels,queryData,searchModels,getFieldsForType,lookupInputType,createData,updateData,deleteData, andmodelSpecificSearch. Because most Sportsbook lists deny create, update, and delete, the write tools are refused for them. There are no sportsbook-specific MCP tools./api/completionis the dashboard AI chat using OpenRouter (OPENROUTER_API_KEY,OPENROUTER_MODEL,OPENROUTER_MAX_TOKENS) with a scoped MCP fetch.
Operations endpoints
/api/healthalways returnsokwith the service name and time./api/readychecks migrations, stale worker leases, degraded providers, oldest pending queue age, worker heartbeats, row-level security coverage, exposure consistency, audit anchoring, and privileged operators without MFA. In production, detail needsMETRICS_BEARER_TOKEN./api/metricsreturns Prometheus text (wagers, queues, providers, dead letters, incidents, markets, journal imbalance). In production it returns 404 without the bearer token.- OpenTelemetry is registered for the app (
instrumentation.ts) and, whenOTEL_EXPORTER_OTLP_ENDPOINTis set, for the worker.
Out of scope in current source: real odds and results feeds, identity and age verification, geolocation, payment processors, AML screening services, tax engines, regulatory reporting transmission, integrity monitoring services, affiliate tracking, and external player-account systems. Each exists only as a sandbox adapter kind.
External storefronts
Give an LLM the Sportsbook storefront skill to adapt the built-in customer experience or create a separate client.
Sportsbook current limitations
What the current Openfront Sportsbook source does not do, including the closed customer API outside sandbox mode, sandbox-only providers, and lists with no writer.