Integration guide
Connect EVM or Solana webhooks, or POST normalized wager events from your indexer. Wallet address is the player ID — triggers run on the same engine for prediction markets and crypto casinos.
Snippets below use https://wagerly.tech as your app origin.
Connect your chain
Primary setup wizard — Alchemy webhook, no app code (~15 min).
# Connect your chain (10–15 min, no app code)
Primary path for all on-chain operators:
1. Open Dashboard → Connect your chain
https://wagerly.tech/dashboard/setup
2. Choose market type
- Prediction market → Polymarket full (Polygon, pre-filled addresses)
- Crypto casino → generic contract on Base
- Other on-chain → pick chain + contract address
3. Copy into Alchemy (Address Activity webhook)
URL: POST https://wagerly.tech/api/v1/ingest/{tenantId}/evm
Header: Authorization: Bearer <ingest_secret>
Watch: contract addresses shown in the wizard
4. Paste Alchemy signing key in the wizard → wait for Retention live
5. Optional on finish step
- Enable Dormancy (pure inactivity) trigger
- Set outbound webhook URL
No API key or SDK required for core retention. Player ID = {chainId}:0xWallet.
Solana: Settings → Advanced connectors → Solana
Advanced HTTP/Supabase: Settings → Advanced connectorsOpen Dashboard → Connect your chain after sign-in.
Quickstart
Advanced paths — HTTP ingest and npm SDK for indexers or legacy off-chain apps.
Recommended: use the setup wizard at /dashboard/setup. Manual reference below.
# EVM webhooks (via setup wizard)
Use Dashboard → /dashboard/setup for copy-paste URL, ingest secret, and watch addresses.
Manual reference:
POST https://wagerly.tech/api/v1/ingest/{tenantId}/evm
Authorization: Bearer <ingest_secret>
Logs decode to wager_placed / wager_settled / deposit_completed.
Player IDs: {chainId}:0xWallet — e.g. 8453:0x742d…
See #connect-chain · #ingest-auth · #connectors · #polymarketGo-live checklist
Connect chain → Retention live → optional trigger and webhook.
# Go-live checklist 1. Open Dashboard → Connect your chain (/dashboard/setup) 2. Choose market type and configure Alchemy Address Activity webhook 3. Paste signing key → confirm Integration health shows "Retention live" 4. Optional: enable dormancy trigger + outbound webhook on wizard finish step 5. Polymarket: use prediction market path (Exchange + USDC pre-filled) Advanced: Settings → Advanced connectors for HTTP, Supabase, Solana, manual EVM. See also: #connect-chain · #metrics · #webhooks · #operations · #events
Track progress in Settings → Integration health and wire connectors at Settings → Connectors.
GGR / NGR metrics
Rolling 7d and 30d gross and net gaming revenue per wallet.
# GGR / NGR metrics Wagerly computes per-wallet rollups from ingested wager events: | Metric | Definition | Source events | |--------|------------|---------------| | GGR (7d / 30d) | Gross gaming revenue — total wager handle | SUM(handle) on wager_placed | | NGR (7d / 30d) | Net gaming revenue — operator margin | SUM(net_margin) on wager_settled | Where to view: - Dashboard → Overview: tenant 7d GGR / NGR rollup - Dashboard → Players: per-wallet 7d GGR / NGR columns - Player detail: 7d + 30d + lifetime GGR/NGR cards Trigger webhook payload (financials block): ggr_7d, ngr_7d, ggr_30d, ngr_30d lifetime_handle, lifetime_net_margin handle_7d, handle_30d
Ingest authentication
Bearer ingest_secret on every webhook ingest route — enforced since Phase 2.
# Ingest authentication (required)
Every webhook ingest route rejects unauthenticated requests:
Authorization: Bearer <ingest_secret>
Routes:
POST /api/v1/ingest/{tenantId} — universal JSON
POST /api/v1/ingest/{tenantId}/evm — Alchemy / QuickNode / raw logs
POST /api/v1/ingest/{tenantId}/supabase — Supabase DB webhook
POST /api/v1/ingest/{tenantId}/solana — Helius / raw Solana txs
POST /api/v1/ingest/{tenantId}/test-mapping — field mapping preview
Copy or rotate ingest_secret in Settings → Credentials or Advanced connectors.
After rotation, update every Alchemy endpoint and curl script — old secrets stop working immediately.
Note: POST /api/v1/track uses your app tracking key (wg_live_…), not the ingest secret.
Optional for browser/SDK integrations — on-chain operators use the setup wizard + ingest routes above.EVM contract templates
Pre-configured decoders in Settings → Connectors → EVM.
# EVM contract templates
First-time setup: use Dashboard → /dashboard/setup (templates pre-selected by market type).
Advanced manual config: Settings → Advanced connectors → EVM → Contract template.
| Template | Use when |
|----------|----------|
| generic_erc20_wager | Custom casino contracts with Trade / Deposit / Settle events |
| polymarket_style | Single-contract Polymarket-style decode (legacy) |
| polymarket_full | Production Polymarket — CTF Exchange + USDC + optional NegRisk |
Advanced fields (trade/deposit/settle event sigs, arg indices) are pre-filled per
template. Override only when your ABI differs.
Player IDs are always stored as {chainId}:0xWallet (e.g. 137:0x742d…).Polymarket setup
Multi-contract ingest for prediction markets on Polygon.
# Polymarket on Polygon (polymarket_full)
1. Settings → Connectors → EVM → select "Polymarket full (multi-contract)"
2. Paste contract addresses (verify on Polygonscan before production):
CTF Exchange (primary — OrderFilled trades/settles):
0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E
USDC (deposit_completed via Transfer):
0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
NegRisk adapter (optional — same OrderFilled sig):
paste your deployment address or leave blank
3. Alchemy / QuickNode webhook:
- Register ALL watched addresses (Exchange + USDC + NegRisk if used)
- Webhook URL: POST …/api/v1/ingest/{tenantId}/evm
- Header: Authorization: Bearer <ingest_secret>
- Prefer GRAPHQL / custom log webhooks that include transaction logs
4. Wagerly decodes:
- OrderFilled on Exchange/NegRisk → wager_placed / wager_settled
- USDC Transfer to player wallet → deposit_completed
- Same wallet maps to player_id 137:0x… across contractsSolana ingest
Helius webhooks and Anchor program presets.
# Solana ingest
URL:
POST https://wagerly.tech/api/v1/ingest/{tenantId}/solana
Authorization: Bearer <ingest_secret>
Player ID format:
solana:{base58Pubkey}
Settings → Connectors → Solana:
- Program ID (your Anchor wager program)
- Template: generic_wager (WagerPlaced / WagerSettled discriminators)
- Optional Helius webhook secret → verifies x-helius-signature
Helius enhanced webhooks: point at the URL above with the ingest secret header.
Raw transaction JSON (logs array) is also supported when you POST manually.Outbound webhooks
Verify trigger.fired deliveries with HMAC-SHA256.
# Outbound trigger webhooks
When a trigger fires with action_type=webhook, Wagerly POSTs JSON to your URL.
Headers:
Content-Type: application/json
X-Wagerly-Event: trigger.fired
X-Wagerly-Signature: sha256=<hex>
Signing:
HMAC-SHA256 of the raw request body bytes, keyed with webhook_signing_secret
(Settings → Webhook → Signing secret). Same signature on "Send test event".
Payload shape (abbreviated):
wagerly_event: trigger.fired
trigger: { id, type, name, fired_at, condition_met, … }
player: { id, last_wager_at, days_since_last_wager, … }
behavior: { current_loss_streak, handle_7d, handle_30d, … }
financials: { ggr_7d, ngr_7d, ggr_30d, ngr_30d, … }
promotion_guidance: { safe_to_promote, suggested_offer, … }# Verify with openssl (replace SECRET and paste exact raw body)
echo -n '{"wagerly_event":"trigger.fired",…}' | openssl dgst -sha256 -hmac "wg_whsec_YOUR_SECRET"import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifyWagerlyWebhook(rawBody: string, signatureHeader: string | null, secret: string) {
if (!signatureHeader?.startsWith('sha256=')) return false
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signatureHeader)
return a.length === b.length && timingSafeEqual(a, b)
}
// Express: use express.raw({ type: 'application/json' }) so you hash the raw bodyCopy your signing secret from Settings → Webhook. Test deliveries from the same page include X-Wagerly-Signature.
Operator tools
Compliance, credentials, dedup, and vertical presets.
# Operator tools (dashboard) Compliance export (Settings → Compliance export) CSV download of trigger_fires + RG intervention rows for a date range. Use for operator audits and regulator requests. Credential history (Settings → Credential history) Last 10 rotations of ingest_secret, API key, and webhook signing secret. Secret values are never stored — only type, timestamp, and actor label. Data health (Dashboard → Players → Data health) Scan for duplicate wallet profiles (e.g. bare 0x vs 137:0x…) after migrating to canonical player IDs. Merge reassigns events and aggregates stats. Vertical presets (Settings → Platform vertical) Changing vertical updates trigger threshold hints. After save, choose "Apply presets" to rewrite seeded trigger thresholds (Loss streak, Dormancy, Tilt).
Framework guides
Complete, production-ready snippets. Paste and replace YOUR_API_KEY.
# .env / .env.local WAGERLY_API_KEY=wg_live_your_key_here # Only needed for browser tracking in Next.js NEXT_PUBLIC_WAGERLY_KEY=wg_live_your_key_here
// lib/wagerly.ts — module-level singleton
import { Wagerly } from '@wagerly/sdk'
export const wagerly = new Wagerly({
apiKey: process.env.WAGERLY_API_KEY!,
})
// app/layout.tsx — browser SDK for front-end tracking
// Add before </body>:
// <script
// src="https://wagerly.dev/sdk.js"
// data-api-key={process.env.NEXT_PUBLIC_WAGERLY_KEY}
// data-user-id={session?.user?.id}>
// </script>
// app/api/bets/route.ts — track wager on placement
import { wagerly } from '@/lib/wagerly'
export async function POST(req: Request) {
const bet = await placeBet(req)
// Fire-and-forget — never block the response
wagerly.wager(userId, {
wager_id: bet.id,
handle: bet.amount,
odds: bet.odds,
currency: 'USD',
market: bet.marketType,
}).catch(console.error)
return Response.json(bet)
}SDK options
Pass to new Wagerly(options)
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | Live API key — starts with wg_live_ |
| baseUrl | string | https://wagerly.dev | Override for on-prem or staging |
| batchSize | number | 20 | Force-flush after this many queued events |
| flushInterval | number | 5000 | Auto-flush interval in ms |
| debug | boolean | false | Verbose console logging |
Event reference
All 16 core event types plus 3 auto-captured browser events.
| Event | SDK method | When to call | Key properties |
|---|---|---|---|
| wager_placed | wagerly.wager() | When a bet is submitted | wager_id, handle, currency, odds, market |
| wager_settled | wagerly.wagerSettled() | When the outcome is known | wager_id, outcome, payout, net_margin |
| wager_voided | wagerly.track() | Bet voided/cancelled | wager_id, handle, currency, void_reason |
| wager_cashed_out | wagerly.track() | Early cashout accepted | wager_id, cashout_amount, net_margin |
| bet_slip_opened | Auto (browser SDK) | Slip opened (has data-wagerly attr) | selections_count, total_stake |
| bet_slip_abandoned | wagerly.track() | Slip closed without placing | selections_count |
| account_created | wagerly.track() | New player signs up | signup_channel, promo_code |
| deposit_initiated | wagerly.track() | Deposit flow started | amount, currency, payment_method |
| deposit_completed | wagerly.deposit() | Deposit cleared/settled | amount, currency, payment_method, transaction_id |
| withdrawal_requested | wagerly.withdrawal() | Withdrawal initiated | amount, currency, payment_method |
| withdrawal_completed | wagerly.track() | Funds sent | amount, currency, transaction_id |
| session_start | wagerly.session("start") | Player logs in / session begins | — |
| session_end | wagerly.session("end") | Player logs out / session ends | duration_seconds |
| login | wagerly.track() | Player authenticates | method |
| promo_claimed | wagerly.track() | Promo code or bonus accepted | promo_id, promo_code, promo_type, value |
| promo_expired | wagerly.track() | Promo window lapsed | promo_id |
Browser SDK auto-captured events
| Event | Trigger | Key properties |
|---|---|---|
| page_view | Every page navigation | url, referrer |
| rage_click | 3+ clicks on same element within 500 ms | element, x, y |
| tab_visibility | Tab gains or loses focus | visible |
Direct API
Legacy track endpoint — on-chain operators should use ingest routes (#ingest-auth) instead.
curl -X POST https://wagerly.dev/api/v1/track \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wg_live_your_key" \
-d '{
"player_id": "user_123",
"event": "wager_placed",
"client_timestamp": "2026-06-23T14:00:00Z",
"properties": {
"wager_id": "bet_001",
"market": "moneyline",
"handle": 50,
"odds": 1.91,
"currency": "USD"
}
}'
# Response: {"ok": true}Endpoint: POST https://wagerly.tech/api/v1/track
Auth: Authorization: Bearer <api_key> (not ingest_secret)
Preferred for on-chain: POST /api/v1/ingest/{tenantId}/evm with ingest secret
Content-Type: application/json
Response: {"ok": true} on success
Responsible gaming
Built-in RG suppression — not bolted on after the fact.
Automatic suppression
Wagerly's trigger engine checks RG status before firing any action. Promotional and retention triggers are automatically blocked for at-risk players — no extra code required.
cooling_off_untilAll triggers suppressed until the date elapsesrg_status = 'intervene'Non-RG promotional triggers suppressedrg_status = 'suspend'All non-RG triggers suppressedSetting RG flags
RG flags (rg_flag_level, rg_status, cooling-off periods) are set from the Responsible Gaming dashboard. Wagerly auto-elevates rg_flag_level based on behavioral signals — operators review and act via the dashboard.
trigger_fires with status='suppressed' and a reason string. Export trigger + RG history as CSV from Settings → Compliance export.