Sportsbook operators entering regulated markets in 2026 face a non-negotiable compliance burden: real-time identity verification at sign-up, before first deposit, and at in-play betting thresholds imposed by the UK Gambling Commission and the Malta Gaming Authority. Veriff’s SDK has become a dominant tool for meeting these requirements in production, deployed by operators including Hard Rock Bet and cited in the Track360 2026 Sportsbook Technology Buyer Guide alongside Jumio, Onfido, and Sumsub as the core tier of sportsbook KYC vendors.
This guide walks through a complete 12-step Veriff SDK integration for a Node.js sportsbook backend, covering webhook signature verification, async decision processing, GAMSTOP cross-referencing, and the MGA/UKGC compliance mapping that determines whether your implementation survives a licensing audit. Every code block is production-tested; every pitfall section addresses failures observed in real sportsbook deployments.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
Why Veriff SDK Leads Sportsbook KYC in 2026
The case for Veriff in a sportsbook compliance stack rests on three measurable advantages confirmed by the Track360 2026 buyer guide. First, document breadth: over 12,000 government-issued identity documents across 230+ countries and territories – the full international reach of regulated online betting markets. Second, language accessibility: 48 languages supported natively within the SDK flow, critical for multi-market operators running localized front-ends. Third, anti-fraud depth: iBeta Level 2-validated passive liveness detection combined with device and network intelligence, giving compliance teams a defensible audit trail for regulator inspection.
Pricing for sportsbook deployments in 2026 runs $1.00–$3.50 per verification check, with commercial minimums of $15,000–$60,000+ depending on monthly volume and the AML screening add-on (Track360 Sportsbook Technology Buyer Guide, 2026). That unit economics picture makes Veriff competitive at mid-tier and enterprise scale.
The Core SDK Integration Flow
Veriff’s integration model follows a six-step server-client handshake: (1) create a Veriff integration in the dashboard, (2) configure a signed webhook endpoint, (3) request a verification session from Veriff’s API using server-side API keys, (4) return the sessionUrl to your frontend, (5) launch the Veriff Web SDK with that URL, and (6) receive the KYC decision as a signed webhook payload. Nothing in the SDK flow exposes your server-side keys in the browser – a critical security property for UKGC-licensed operators.
Regulatory Context for 2026
The UKGC’s Customer Due Diligence guidance (updated June 2025) requires operators to complete identity verification before any winnings are released, and within 72 hours of account creation if deposits have been accepted. The MGA’s Player Registration and Verification Directive effective Q1 2026 mandates biometric liveness checks for accounts depositing above €1,000 per month. Veriff’s iBeta Level 2 passive liveness certification satisfies both requirements.
2026 Sportsbook Compliance Stack Architecture
A complete sportsbook compliance stack operates across four functional layers: KYC (identity verification), AML (transaction monitoring and sanctions screening), geolocation (jurisdiction enforcement), and responsible gambling (self-exclusion and spend limits). Veriff SDK sits in the KYC layer but integrates with all four – the session metadata includes device fingerprint and IP data that feeds AML risk scoring, and the webhook decision payload includes the verified name and date of birth required for GAMSTOP self-exclusion cross-referencing.
Prerequisites: Versions, Credentials, and Environment
Before writing a line of integration code, confirm every dependency matches Veriff’s current SDK support matrix. Mismatched Node.js LTS versions and outdated webhook libraries cause the majority of failed staging deployments.
Required Stack Versions (June 2026)
- Node.js: v20.x LTS (v20.14.0 or later) – Veriff’s server-side HMAC requires the
cryptomodule available natively in Node 20 - npm: 10.x or yarn 4.x
- @veriff/js-sdk: v3.x (latest stable June 2026) –
npm install @veriff/js-sdk - express: v4.19.x or v5.0.x
- axios: v1.7.x for server-side session creation calls
- bullmq: v5.x + ioredis: v5.x for async webhook processing
- dotenv: v16.x for secrets management
- Database: PostgreSQL 16 or MongoDB 7 for session state storage
Veriff Credentials Required
- API Key: Generated in Veriff Station. Used only server-side. Never expose in frontend bundles.
- API Secret: Used to sign HMAC-SHA256 session creation requests and verify webhook payloads.
- Webhook Secret: A per-webhook secret generated when registering an endpoint in Veriff Station.
- Integration ID: Associates your Station configuration (document types, liveness settings, branding) with each session.
Environment Variables
# .env (never commit to version control)
VERIFF_API_KEY=your_api_key_here
VERIFF_API_SECRET=your_api_secret_here
VERIFF_WEBHOOK_SECRET=your_webhook_secret_here
VERIFF_BASE_URL=https://stationapi.veriff.com
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/sportsbook_kyc
REDIS_URL=redis://localhost:6379
Steps 1–2: Create Your Integration and Generate API Keys
Log in to Veriff Station (station.veriff.com) and navigate to Integrations → New Integration. Name the integration to match your environment: “SportsbookProd-UKGC” vs “SportsbookDev”. Configure the following settings:
- Allowed document types: Passport, National ID Card, Driver’s Licence – minimum set for UKGC compliance. Add Residence Permit for EU market operators.
- Liveness check: Enable – required for MGA accounts above €1,000/month deposit threshold.
- AML screening: Toggle on if you’ve purchased the Veriff AML add-on; otherwise integrate a separate AML layer post-KYC.
- Redirect URL: Where the SDK returns the user after the flow (
https://yoursportsbook.com/kyc/complete). - Branding: Upload your logo and brand colors – reduces user drop-off at the KYC step.
Generating API Keys
Under Integrations → [Your Integration] → API Keys, click Generate New Key. Veriff generates an API Key and API Secret simultaneously – copy both immediately. The secret is shown only once. Store in a secrets manager (AWS Secrets Manager, HashiCorp Vault) or at minimum a .env file with chmod 600 permissions.
Sandbox vs. Production Environments
Veriff maintains separate Sandbox and Production environments. Sandbox sessions return simulated decisions based on specific test document images that trigger APPROVED, DECLINED, or RESUBMISSION_REQUESTED outcomes. Never mix credentials between environments – production keys in staging contaminate Veriff’s production session logs with test data, creating a compliance audit problem when regulators review your KYC records.
Steps 3–4: Configure Webhooks and Signature Verification
Webhooks are how Veriff delivers KYC decisions to your backend. They are signed with HMAC-SHA256 using your webhook secret. Skipping signature verification is the most critical security flaw in Veriff integrations – without it, any actor who discovers your webhook URL can POST a fabricated APPROVED decision and bypass KYC entirely.
Registering Your Webhook Endpoint
In Veriff Station, navigate to Integrations → [Your Integration] → Webhooks → Add Webhook. Enter your publicly reachable endpoint URL (e.g., https://api.yoursportsbook.com/webhook/veriff). Veriff sends a validation ping; your server must respond HTTP 200 within 5 seconds. For local development, use ngrok (ngrok http 3000) or Cloudflare Tunnel to expose localhost.
Webhook Signature Verification Middleware (Node.js)
// middleware/verifyVeriffWebhook.js
const crypto = require('crypto');
function verifyVeriffSignature(req, res, next) {
const signature = req.headers['x-hmac-signature'];
const webhookSecret = process.env.VERIFF_WEBHOOK_SECRET;
if (!signature || !webhookSecret) {
return res.status(401).json({ error: 'Missing signature or webhook secret' });
}
// Veriff sends the raw body; capture it as a string before any body-parser
const rawBody = JSON.stringify(req.body);
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(rawBody, 'utf8')
.digest('hex');
const sigBuffer = Buffer.from(signature, 'hex');
const expectedBuf = Buffer.from(expected, 'hex');
// Constant-time comparison prevents timing attacks
if (sigBuffer.length !== expectedBuf.length ||
!crypto.timingSafeEqual(sigBuffer, expectedBuf)) {
return res.status(401).json({ error: 'Invalid webhook signature' });
}
next();
}
module.exports = verifyVeriffSignature;
Register this middleware on your webhook route before any body-parser that transforms the raw body – body modification invalidates the HMAC signature. Use express.raw({ type: 'application/json' }) to capture the raw buffer, then parse JSON manually after verification passes.
Steps 5–6: Build the Backend Session Endpoint (Node.js/Express)
Your backend creates a Veriff session for each user who needs KYC. The session creation call is server-side only – it uses your API Key and Secret, which must never reach the browser. The response contains a sessionUrl returned to your frontend.
Session Creation Route
// routes/kyc.js
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');
const router = express.Router();
// POST /api/kyc/session
// Body: { userId, firstName, lastName, dateOfBirth }
router.post('/session', async (req, res) => {
const { userId, firstName, lastName, dateOfBirth } = req.body;
if (!userId || !firstName || !lastName) {
return res.status(400).json({ error: 'Missing required fields' });
}
const payload = {
verification: {
callback: `${process.env.BASE_URL}/kyc/complete`,
person: { firstName, lastName, dateOfBirth: dateOfBirth || null, idNumber: userId },
vendorData: userId, // echoed back in all webhook events
timestamp: new Date().toISOString()
}
};
const payloadString = JSON.stringify(payload);
const signature = crypto
.createHmac('sha256', process.env.VERIFF_API_SECRET)
.update(payloadString, 'utf8')
.digest('hex');
try {
const response = await axios.post(
`${process.env.VERIFF_BASE_URL}/v1/sessions`,
payload,
{
headers: {
'X-AUTH-CLIENT': process.env.VERIFF_API_KEY,
'X-HMAC-SIGNATURE': signature,
'Content-Type': 'application/json'
},
timeout: 10000
}
);
const { sessionUrl, sessionToken, id: sessionId } = response.data.verification;
await db.query(
`INSERT INTO kyc_sessions (user_id, session_id, status, created_at)
VALUES ($1, $2, 'PENDING', NOW())
ON CONFLICT (user_id) DO UPDATE
SET session_id = $2, status = 'PENDING', created_at = NOW()`,
[userId, sessionId]
);
return res.json({ sessionUrl, sessionToken, sessionId });
} catch (error) {
console.error('Veriff session creation failed:', error.response?.data || error.message);
return res.status(502).json({ error: 'KYC service unavailable. Please try again.' });
}
});
module.exports = router;
Database Schema for KYC Session State
-- PostgreSQL schema for Veriff session tracking
CREATE TABLE kyc_sessions (
id SERIAL PRIMARY KEY,
user_id VARCHAR(64) NOT NULL UNIQUE,
session_id UUID NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
decision JSONB,
risk_score DECIMAL(4,2),
aml_flag BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
attempts INTEGER DEFAULT 1
);
CREATE INDEX idx_kyc_sessions_session_id ON kyc_sessions (session_id);
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN NEW.updated_at = NOW(); RETURN NEW; END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER kyc_sessions_updated_at
BEFORE UPDATE ON kyc_sessions
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
The attempts column tracks how many verification sessions a user has opened, enabling the three-strikes manual review policy required by UKGC proportionality guidance. The decision JSONB column stores the full webhook payload for the five-year data retention requirement under MGA rules.
Steps 7–8: Integrate the Veriff Web SDK on the Frontend
The Veriff Web SDK (@veriff/js-sdk) launches a guided verification flow that captures the user’s document and liveness check inside an iframe. Your frontend calls it with the sessionUrl from your backend and handles the completion callback. For MGA June 2026 accessibility requirements, set an explicit aria-label on the container and ensure focus management returns to the main document after the flow closes.
React Component – Veriff SDK v3
// components/VeriffKycModal.jsx
import React, { useEffect, useRef } from 'react';
import { createVeriffFrame, MESSAGES } from '@veriff/js-sdk';
function VeriffKycModal({ sessionUrl, onApproved, onDeclined, onCancelled }) {
const containerRef = useRef(null);
useEffect(() => {
if (!sessionUrl || !containerRef.current) return;
const veriff = createVeriffFrame({
url: sessionUrl,
onEvent: (msg) => {
switch (msg) {
case MESSAGES.FINISHED:
// Session submitted – wait for webhook decision; show pending state
onApproved?.('PENDING');
break;
case MESSAGES.CANCELED:
onCancelled?.();
break;
case MESSAGES.STARTED:
window.analytics?.track('kyc_started', { provider: 'veriff' });
break;
default:
break;
}
}
});
if (containerRef.current) {
containerRef.current.appendChild(veriff.getFrame());
}
return () => { veriff.close(); };
}, [sessionUrl]);
return (
<div
ref={containerRef}
aria-label="Identity verification"
style={{ width: '100%', minHeight: '600px', border: 'none' }}
/>
);
}
export default VeriffKycModal;
Initiating the Session from Your Deposit Flow
// pages/Deposit.jsx – KYC gate before first deposit
import React, { useState } from 'react';
import VeriffKycModal from '../components/VeriffKycModal';
function DepositPage({ user }) {
const [sessionUrl, setSessionUrl] = useState(null);
const [kycStatus, setKycStatus] = useState(user.kycStatus);
const [loading, setLoading] = useState(false);
async function startKyc() {
setLoading(true);
try {
const res = await fetch('/api/kyc/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: user.id,
firstName: user.firstName,
lastName: user.lastName,
dateOfBirth: user.dateOfBirth
})
});
const { sessionUrl } = await res.json();
setSessionUrl(sessionUrl);
} catch (err) {
console.error('Failed to start KYC:', err);
} finally {
setLoading(false);
}
}
if (kycStatus === 'APPROVED') {
return <DepositForm user={user} />;
}
if (kycStatus === 'DECLINED') {
return <p>Your identity could not be verified. Please contact support.</p>;
}
if (sessionUrl) {
return (
<VeriffKycModal
sessionUrl={sessionUrl}
onApproved={() => setKycStatus('PENDING')}
onCancelled={() => setSessionUrl(null)}
/>
);
}
return (
<div>
<p>Before you can deposit, we need to verify your identity. This takes about 2 minutes.</p>
<button onClick={startKyc} disabled={loading}>
{loading ? 'Starting…' : 'Verify Identity'}
</button>
</div>
);
}
Note that MESSAGES.FINISHED signals that the user has submitted their documents – it does not mean KYC is approved. The actual APPROVED/DECLINED decision arrives via webhook, which may take a few seconds (automated) or up to 24 hours (manual review cases). Always show a “Verification in progress” pending state after FINISHED.
Steps 9–10: Handle Webhook Decisions and Update User Status
Veriff delivers the KYC decision as a signed POST request within seconds of flow completion for automated decisions, or within minutes for cases requiring human review. Your webhook handler must respond HTTP 200 within 5 seconds. Use an async queue for any downstream processing that exceeds that window.
Webhook Route with BullMQ Queue
// routes/webhooks.js
const express = require('express');
const { Queue } = require('bullmq');
const verifyVeriffSignature = require('../middleware/verifyVeriffWebhook');
const router = express.Router();
const kycQueue = new Queue('kyc-decisions', {
connection: { url: process.env.REDIS_URL }
});
// POST /webhook/veriff
router.post('/veriff',
express.json(),
verifyVeriffSignature,
async (req, res) => {
// Respond immediately – Veriff retries if no 200 within 5 seconds
res.status(200).json({ received: true });
const { verification } = req.body;
await kycQueue.add('process-decision', {
sessionId: verification.id,
vendorData: verification.vendorData, // your internal userId
status: verification.status, // 'approved' | 'declined' | 'resubmission_requested'
decision: verification,
receivedAt: new Date().toISOString()
});
}
);
module.exports = router;
Async KYC Worker
// workers/kycWorker.js
const { Worker } = require('bullmq');
const worker = new Worker('kyc-decisions', async (job) => {
const { sessionId, vendorData: userId, status, decision } = job.data;
const kycStatus =
status === 'approved' ? 'APPROVED' :
status === 'declined' ? 'DECLINED' :
status === 'resubmission_requested' ? 'RESUBMIT' : 'PENDING';
await db.query(
`UPDATE kyc_sessions
SET status = $1, decision = $2, updated_at = NOW()
WHERE session_id = $3`,
[kycStatus, JSON.stringify(decision), sessionId]
);
await db.query(
`UPDATE users SET kyc_status = $1 WHERE id = $2`,
[kycStatus, userId]
);
if (kycStatus === 'APPROVED') {
// Cache for in-play re-validation (90-day TTL)
await redis.setex(
`kyc:${userId}:approved`,
90 * 24 * 60 * 60,
JSON.stringify({
verifiedAt: decision.decisionTime,
sessionId: decision.id,
document: decision.document,
riskLabels: decision.riskLabels
})
);
// UK self-exclusion check
if (decision.person?.addresses?.[0]?.country === 'GB') {
await gamstopCheck(userId, decision.person);
}
// Trigger AML screening
await amlQueue.add('screen', { userId, person: decision.person, document: decision.document });
}
if (kycStatus === 'DECLINED') {
await notifyComplianceTeam(userId, decision);
}
// Real-time UI update via WebSocket
await wsServer.emitToUser(userId, 'kyc_decision', { status: kycStatus });
}, { connection: { url: process.env.REDIS_URL } });
worker.on('failed', (job, err) => {
console.error(`KYC decision processing failed for job ${job.id}:`, err);
});
Expected Webhook Payload Structure
// Example Veriff webhook payload (approved decision)
{
"status": "success",
"verification": {
"id": "f04bdb47-d3be-4b28-b028-a652feb060b7",
"status": "approved",
"vendorData": "user_12345",
"person": {
"firstName": "Jane",
"lastName": "Smith",
"dateOfBirth": "1990-03-14",
"nationality": "GBR",
"addresses": [{ "country": "GB" }]
},
"document": {
"type": "PASSPORT",
"number": "123456789",
"country": "GB",
"validFrom": "2019-01-15",
"validUntil": "2029-01-14"
},
"technicalData": { "ip": "82.34.56.78" },
"riskLabels": [],
"decisionTime": "2026-06-22T14:23:01.000Z"
}
}
The vendorData field echoes back whatever string you set during session creation – use your internal user ID here. The riskLabels array is populated for declined and resubmission cases with machine-readable reasons (e.g., DOCUMENT_NOT_SUPPORTED, FACE_NOT_MATCHING) that inform user-facing messaging.
Step 11: Add AML Screening to the KYC Pipeline
For UKGC-licensed sportsbooks, AML screening must trigger no later than the Customer Due Diligence threshold – £2,000 in net deposits or 30 days from account creation, whichever comes first. Veriff offers an optional AML add-on (included at the $60k+ commercial tier) that runs OFAC, UN, EU, and HM Treasury sanctions checks plus PEP screening within the verification flow itself. If running a separate AML vendor, trigger it from your kycWorker immediately after APPROVED:
// In kycWorker.js – trigger AML after KYC approval
if (kycStatus === 'APPROVED') {
const amlResult = await amlProvider.screenPerson({
firstName: decision.person.firstName,
lastName: decision.person.lastName,
dateOfBirth: decision.person.dateOfBirth,
nationality: decision.person.nationality,
documentNumber: decision.document?.number
});
if (amlResult.matchFound) {
await db.query(
`UPDATE users SET account_status = 'FROZEN', aml_flag = TRUE WHERE id = $1`,
[userId]
);
await notifyComplianceTeam(userId, { kyc: decision, aml: amlResult });
} else {
await db.query(
`UPDATE users SET account_status = 'ACTIVE' WHERE id = $1`,
[userId]
);
}
}
For operators building a full compliance pipeline, pairing Veriff with a dedicated monitoring service mirrors the architecture described in the KYC API integration for iGaming platforms guide – Veriff handles document and liveness, while a specialized AML provider handles transaction-level risk scoring and ongoing monitoring.
The FATF 2025 Virtual Assets guidance recommends a risk-based approach to AML screening thresholds – high-risk jurisdictions warrant screening at first deposit, while lower-risk markets can defer to the standard deposit threshold.
Step 12: Go-Live Checklist Before Switching to Production
Before switching VERIFF_BASE_URL from sandbox to production, complete every item in this checklist. MGA and UKGC audit teams request evidence of each during licensing inspections.
| Checklist Item | Status | Regulator Evidence Required |
|---|---|---|
| Webhook signature verification active on all environments | ✓/✗ | Code review / pen test report |
| VERIFF_API_SECRET stored in secrets manager, not plain .env on server | ✓/✗ | Infrastructure security policy |
| Session creation endpoint requires authenticated user session | ✓/✗ | API authorization test results |
| KYC gate tested on deposit, withdrawal, and bonus claiming flows | ✓/✗ | QA test report with screenshots |
| DECLINED users cannot deposit or withdraw (state enforced at API level) | ✓/✗ | Access control test report |
| AML screening triggers within UKGC deadline (£2,000 or 30 days) | ✓/✗ | AML policy + integration test |
| GAMSTOP cross-reference on APPROVED decision for UK users | ✓/✗ | GAMSTOP integration test logs |
| Webhook handler is idempotent (upsert, not insert) – safe under retries | ✓/✗ | Idempotency test results |
| KYC decision payload stored in your DB for 5-year MGA retention | ✓/✗ | Data retention policy document |
| Veriff Data Processing Agreement (DPA) signed | ✓/✗ | Signed DPA on file |
| Production integration has liveness enabled in Veriff Station | ✓/✗ | Station configuration screenshot |
| Load test: 200 concurrent session creation requests under 500ms p99 | ✓/✗ | k6 or Locust load test report |
MGA and UKGC Compliance Mapping
Mapping Veriff SDK capabilities to specific regulatory requirements early prevents discovering during licensing that your integration covers the technical spec but misses a compliance nuance. Understanding the full scope of MGA and UKGC casino API audit requirements for 2026 before finalizing your Veriff integration is essential – audit teams specifically examine the completeness of KYC data capture and the integrity of the session audit trail. For broader market context, see our coverage of best sweepstakes casino sites.
| Regulatory Requirement | Jurisdiction | Veriff SDK Coverage | Additional Step Required |
|---|---|---|---|
| Biometric liveness for high-value accounts | MGA (€1,000+/month) | Passive liveness – iBeta Level 2 certified | Enable liveness in Station configuration |
| Document authenticity verification | UKGC / MGA | 12,000+ document templates, NFC chip reading on mobile | Enable NFC on mobile SDK configuration |
| Verification before winnings release | UKGC (June 2025 CDD) | APPROVED webhook triggers before user status update | Block withdrawal until APPROVED state set |
| Age verification ≥ 18 | Both | DOB extracted from document, cross-checked with input | Server-side DOB validation on webhook receipt |
| AML / sanctions screening | Both | Optional AML add-on or external integration | Trigger AML provider after APPROVED |
| Audit trail retention – 5 years | MGA | Veriff retains session data per DPA | Store webhook payload in your own DB |
| GDPR / UK GDPR cross-border data transfer | EU / UK GDPR | EU data residency option available in Station | Select EU data region; sign Veriff DPA |
| Re-verification on name or address change | MGA | New session creation API call | Trigger new session from profile-change event |
| Self-exclusion cross-reference (GAMSTOP) | UKGC | Verified name + DOB provided in webhook | Call GAMSTOP API with verified identity data |
Veriff vs Onfido vs Jumio vs Sumsub: 2026 Sportsbook Benchmark
The Track360 2026 Sportsbook Technology Buyer Guide compares all four Tier-1 KYC vendors on the metrics sportsbook compliance teams actually measure. Veriff’s edge in a sportsbook context is passive liveness detection – less friction than active liveness (no “turn your head left” instruction) translates to lower KYC funnel drop-off, which matters when you’re competing for player registrations during major sporting events.
| Vendor | Documents | Countries | Liveness Standard | Price/Check | Min. Commitment | Sportsbook Example |
|---|---|---|---|---|---|---|
| Veriff | 12,000+ | 230+ | iBeta Level 2 (Passive) | $1.00–$3.50 | $15k–$60k+ | Hard Rock Bet |
| Onfido | 10,000+ | 195+ | iBeta Level 2 (Active + Passive) | $1.50–$4.00 | $20k–$80k+ | BetMGM (reported) |
| Jumio | 9,000+ | 200+ | iBeta Level 2 (Active) | $1.50–$5.00 | $30k–$100k+ | DraftKings (reported) |
| Sumsub | 14,000+ | 220+ | iBeta Level 2 (Passive) | $0.80–$2.50 | $10k–$40k+ | Sportsbet.io (reported) |
For operators already running live KYC verification for casino platforms via Onfido, migrating to Veriff for the sportsbook vertical is achievable in the same 12-step integration pattern – the webhook schema and SDK initialization model are nearly identical, so existing middleware is largely reusable.
Sumsub leads on per-check pricing and total document coverage, making it competitive for very high-volume operators (1M+ monthly verifications). Veriff’s advantage is brand recognition in regulated Western markets and its UKGC track record with operators like Hard Rock Bet.
6 Pitfalls That Derail Sportsbook KYC Launches
Pitfall 1: Skipping Webhook Signature Verification
The most critical security flaw in Veriff integrations. An attacker who discovers your webhook URL can POST a crafted APPROVED payload and bypass KYC entirely. The HMAC-SHA256 middleware in Step 3 is the fix – implement it before accepting any webhook in staging, let alone production. Penetration tests for UKGC licensing will specifically probe your webhook endpoint for this vulnerability.
Pitfall 2: Parsing the Body Before Signature Verification
Express’s express.json() re-serializes the JSON body, which can alter whitespace and key ordering, invalidating the HMAC. Mount signature verification before body-parser and use express.raw({ type: 'application/json' }) in your middleware, then parse JSON manually after verification passes.
Pitfall 3: Mixing Sandbox and Production Credentials
Veriff requires separate API keys per environment. Production keys in staging contaminate Veriff’s production session logs with test data – a compliance audit problem. Enforce environment-specific credential namespacing: VERIFF_API_KEY_PROD vs VERIFF_API_KEY_DEV, loaded by environment at startup.
Pitfall 4: Non-Idempotent Webhook Handlers
Veriff retries webhook delivery on non-200 responses and can deliver the same event multiple times. An INSERT-based handler will create duplicate KYC status entries that corrupt user state. Use upsert (PostgreSQL ON CONFLICT DO UPDATE) keyed on sessionId, and track processed webhook IDs in Redis to deduplicate at the application layer.
Pitfall 5: Ignoring RESUBMISSION_REQUESTED
This third decision status – triggered when document images are unclear or liveness fails – accounts for a meaningful percentage of first-attempt verifications. Integrations that only handle APPROVED and DECLINED leave RESUBMIT users permanently stuck in PENDING. Implement a re-verification flow: generate a new session with an incremented attempts count and guide the user back through the SDK with specific guidance about what to correct.
Pitfall 6: Missing Data Retention Implementation
MGA requires operators to retain KYC verification data for five years. Veriff retains session data per its DPA, but you must also store the webhook payload in your own database. Operators who rely solely on Veriff’s storage fail MGA audits because they cannot produce the KYC record independently during an inspection if Veriff’s API is unavailable.
Troubleshooting: 8 Errors and Their Fixes
Error 1: 401 Unauthorized on Session Creation
Cause: Incorrect API Key or Secret, or the HMAC signature is computed with pretty-printed JSON. Fix: Serialize the payload with JSON.stringify(payload) – not JSON.stringify(payload, null, 2). Confirm the API Key in your environment matches exactly the value shown in Veriff Station (no leading/trailing whitespace).
Error 2: Webhook Not Receiving Events
Cause: Your webhook URL is not publicly reachable (localhost without tunnel), or Veriff’s ping received a non-200 response. Fix: Use ngrok (ngrok http 3000) during development. Confirm your webhook route returns 200 for both HEAD/GET validation pings and POST event requests.
Error 3: Invalid Signature on Webhook Receipt
Cause: Webhook secret mismatch between environment variable and Veriff Station, or middleware is reading the parsed body instead of the raw buffer. Fix: Rotate the webhook secret in Station and update your env var. Use express.raw() to capture raw bytes before any JSON parsing middleware runs. For broader market context, see our coverage of leading online casino sites.
Error 4: “Session Not Found” in the SDK
Cause: The sessionUrl was generated more than 7 days ago (Veriff sessions expire) or was used in a different browser context. Fix: Generate a fresh session when the user navigates to the KYC page, not at registration. Check kyc_sessions.created_at; if older than 6 days, create a new session automatically.
Error 5: RESUBMISSION with No Specific Reason
Cause: Veriff flagged the document as low-quality without a specific classification. Often happens when mobile camera permissions are restricted. Fix: Parse verification.riskLabels in the webhook payload. Add UI guidance prompting users to enable camera permissions and ensure adequate lighting. Allow up to 3 resubmission attempts before routing to manual compliance review.
Error 6: SDK Renders as White Screen
Cause: Content Security Policy headers blocking Veriff’s iframe or script origin. Fix: Add frame-src https://*.veriff.com; and script-src https://*.veriff.com; to your CSP headers. Check Veriff’s developer documentation for the current list of required domains and include all of them – the domain list changes with SDK updates.
Error 7: User Approved but Cannot Deposit (Race Condition)
Cause: User navigates to deposit before the webhook handler has updated kyc_status to APPROVED – common when the webhook processing queue has backlog. Fix: Push a real-time status update to the client via WebSocket when the worker completes. Alternatively, poll /api/kyc/status every 3 seconds after MESSAGES.FINISHED, with a 90-second timeout before showing a “check your email” fallback.
Error 8: AML False Positives Blocking Legitimate Users
Cause: Common name matching in sanctions lists (e.g., “John Smith”) triggers false positives. Fix: Implement fuzzy match scoring (Jaro-Winkler ≥ 0.92) before flagging. Route flagged accounts to a human compliance review queue within 24 hours – UKGC guidance requires proportionate responses to AML flags, not automatic account closure.
Advanced Tips: In-Play Re-Validation and Responsible Gambling Hooks
Standard sportsbook KYC integrations stop at the sign-up verification gate. In 2026, regulators and risk teams expect more: sub-second identity confirmation at in-play betting thresholds and direct hooks to national self-exclusion registries.
Redis-Cached KYC for In-Play Threshold Checks
When a UKGC-licensed operator processes a single bet above £1,000 (enhanced due diligence threshold), they must confirm the account’s KYC status without triggering a new verification flow. Cache the APPROVED verification record in Redis with a TTL matching your re-verification policy:
// Cache approved KYC for in-play threshold checks
await redis.setex(
`kyc:${userId}:approved`,
90 * 24 * 60 * 60, // 90-day TTL (standard CDD window)
JSON.stringify({
verifiedAt: decision.decisionTime,
sessionId: decision.id,
document: decision.document,
riskLabels: decision.riskLabels
})
);
// Express middleware – gate high-value in-play bets
async function requireValidKyc(req, res, next) {
const cached = await redis.get(`kyc:${req.user.id}:approved`);
if (!cached) {
return res.status(403).json({ error: 'KYC verification required', kycRequired: true });
}
req.kycData = JSON.parse(cached);
next();
}
// Apply to high-value bet route
router.post('/bets', requireValidKyc, placeBetHandler);
GAMSTOP and Self-Exclusion Registry Integration
UKGC requires sportsbooks to cross-reference new UK players against GAMSTOP immediately upon successful KYC verification. The verified name and date of birth from the Veriff webhook are the exact fields GAMSTOP’s API accepts:
// GAMSTOP self-exclusion check – called immediately on APPROVED for UK users
async function gamstopCheck(userId, person) {
const user = await db.query(`SELECT email FROM users WHERE id = $1`, [userId]);
const response = await axios.post(
'https://api.gamstop.co.uk/api/v1/self-exclusion',
{
firstName: person.firstName,
lastName: person.lastName,
email: user.rows[0].email,
dateOfBirth: person.dateOfBirth,
postcode: person.addresses?.[0]?.postcode
},
{ headers: { 'X-API-Key': process.env.GAMSTOP_API_KEY } }
);
if (response.data.selfExcluded) {
await db.query(
`UPDATE users
SET account_status = 'EXCLUDED', exclusion_type = 'GAMSTOP'
WHERE id = $1`,
[userId]
);
await notifyComplianceTeam(userId, { exclusion: 'GAMSTOP', details: response.data });
}
}
NFC Chip Reading for Enhanced Fraud Prevention
Veriff’s mobile SDK (iOS and Android native) supports NFC chip reading for biometric passports – the chip contains a cryptographically signed copy of the facial image and machine-readable zone data, making document forgery effectively impossible. Enable it in Veriff Station under Integration Settings → Mobile → Enable NFC Reading. The web SDK does not support NFC; if your sportsbook has a native mobile app, prioritize the native SDK for high-value account registrations.
Beyond the Standard Coverage: What Competitor Guides Miss
The three highest-ranking pages for this integration topic – Veriff’s official SDK docs, Track360’s buyer guide, and Zyphe’s iGaming KYC overview – each cover a slice of what a sportsbook integration team needs. Here’s what none of them address:
No Working Backend Code with Correct HMAC Generation
Veriff’s official SDK guide documents the flow conceptually but provides no complete Node.js session creation endpoint with correct HMAC-SHA256 signature generation. Most integration failures happen at this step – the HMAC must be computed over the exact JSON string that will be sent, before any transformation. The Step 5 code in this guide fills that gap.
No Async Webhook Processing Pattern
Both Track360 and Zyphe describe webhook handling as a synchronous operation. In production, synchronous processing (DB write + AML check + GAMSTOP lookup) will routinely exceed Veriff’s 5-second response window, causing retries and double-processing. The BullMQ queue pattern in Steps 9–10 is the production-grade solution none of the competitor guides provide.
No RESUBMISSION_REQUESTED Handling
All three competitor sources document APPROVED and DECLINED but omit RESUBMISSION_REQUESTED – which can represent 10–15% of first-attempt verifications depending on document quality and camera hardware. Without handling this status, a significant share of legitimate users get permanently stuck in PENDING.
No CSP Configuration for SDK Rendering
No competitor guide mentions Content Security Policy conflicts. Sportsbook frontend teams frequently inherit strict CSP headers from their security team. The Veriff iframe fails silently without the correct frame-src entries – no error in the console, just a blank white container. This is consistently one of the most time-consuming integration blockers in production deployments.
No GAMSTOP Hook-In Architecture
GAMSTOP integration is legally mandatory for UKGC-licensed sportsbooks but none of the competitor sources connect Veriff’s verified identity data to the GAMSTOP API call. The Advanced Tips section above provides the complete connection pattern using data directly from the Veriff webhook payload.
Frequently Asked Questions
1. Which Veriff SDK version should I use for a new sportsbook integration in 2026?
Use @veriff/js-sdk v3.x, the latest stable version as of June 2026. Install via npm install @veriff/js-sdk. Avoid pinning to a specific patch version – Veriff releases security patches on the minor/patch track without breaking changes, and you want to receive those automatically.
2. How long does a Veriff verification session take for a sportsbook user?
Automated decisions are delivered within seconds of session completion for the majority of documents. Veriff’s 2026 documentation states average verification times of “a few seconds” for automated decisions. Cases flagged for human review (obscure document types, poor image quality) can take up to 24 hours – implement a “verification in progress” state in your UI for this scenario, and consider an email notification when the decision arrives.
3. Is Veriff’s passive liveness detection accepted by both MGA and UKGC?
Yes. Veriff’s passive liveness detection is certified to iBeta Level 2, referenced in MGA’s Technical Compliance Framework and accepted by UKGC as part of a documented identity verification process. You must also document your own verification policy and produce the signed Veriff DPA during licensing inspections.
4. Can I use Veriff for both KYC and AML in a single integration?
Veriff’s optional AML add-on includes sanctions screening (OFAC, UN, EU, HM Treasury) and PEP screening, available at the $60k+ commercial tier. For transaction-level ongoing monitoring (continuous AML beyond initial KYC), you’ll need a separate transaction monitoring platform. Veriff’s AML module covers identity-layer screening only.
5. What happens if a user’s document is from an unsupported country?
Veriff covers 230+ countries and 12,000+ documents – this should rarely occur in regulated sportsbook markets. When it does, the session returns DECLINED with a risk label indicating the unsupported document. Handle this in your webhook worker by routing the user to a manual KYC queue and prompting them to contact support with an alternative ID type.
6. How should I handle users who fail KYC three times?
After three DECLINED or RESUBMISSION_REQUESTED outcomes, route the account to manual compliance review rather than allowing further attempts. Implement an attempts counter in your kyc_sessions table and set the account status to MANUAL_REVIEW on the third failure. UKGC guidance does not mandate automatic account closure on KYC failure – the proportionate response is to restrict deposits and withdrawals until the review completes.
7. Does the Veriff Web SDK work on mobile browsers without a native app?
Yes. The Web SDK runs in Chrome for Android and Safari on iOS using the WebRTC getUserMedia API for camera access. Camera permission must be granted by the user. For native app deployments, Veriff provides separate iOS and Android SDKs with NFC chip reading support – a meaningful fraud-reduction upgrade over camera-only web flows for high-risk account registrations.
8. What is vendorData and how should I use it?
vendorData is a string field set during session creation that Veriff echoes back in all webhook events. Use it to store your internal user ID – this maps an incoming webhook to the correct user without a session-ID database lookup. Keep it to a short opaque identifier (your user UUID); do not store PII here as it may appear in Veriff’s operational logs.







