Your backend creates a session by calling the registration endpoint. The response includes a QR code and redirect URL for the user.
Individual (KYC)
Business (KYB)
cURL
Copy
Ask AI
curl -X POST "https://api.identity.predicate.io/api/v1/register/individual?size=300" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY"
cURL
Copy
Ask AI
curl -X POST "https://api.identity.predicate.io/api/v1/register/business?size=300" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY"
Request Body (Optional):
Copy
Ask AI
{ "walletAddress": "0x1234567890abcdef..."}
Wallet Constraint: When you include walletAddress in the request:
The user can only connect and verify with that specific wallet address
If a session (pending, retry, or completed) already exists for that wallet with the same verification type, the existing session is returned (session reuse)
Sessions are only reused within the same check type (KYC sessions for /register/individual, KYB for /register/business)
Useful for pre-registering known wallet addresses or enforcing wallet constraints
HTTP Status Codes:
201 Created - A new session was created
200 OK - An existing session was reused (when walletAddress matched an existing reusable session)
Display the base64-encoded QR code from the response directly in an <img> tag.
Copy
Ask AI
<div class="kyc-section"> <h2>Complete Your Verification</h2> <p>Scan the QR code or click the link below to start:</p> <!-- QR Code (from API response) --> <img id="kyc-qr" alt="Verify Identity" style="width: 300px; height: 300px;" /> <!-- Direct link --> <p><a id="kyc-link" target="_blank">Verify Your Identity</a></p></div><script>async function initiateKYC(type) { const response = await fetch('/kyc/create-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: type || 'individual' }) }); const data = await response.json(); // Display QR code (already base64-encoded with data URI prefix) document.getElementById('kyc-qr').src = data.qrCode; document.getElementById('kyc-link').href = data.redirectUrl; // Start monitoring for completion startMonitoring(data.sessionId, type || 'individual');}</script>
What happens on the verification portal:
User lands on the verification page
User connects their wallet(s) - up to 20 per session, across different chains
Fixable issue (e.g., blurry document), user can retry
Important: A submitted status means documents are under manual review. The wallet is not verified until status becomes completed.
SSE (Recommended)
Polling
Server-Sent Events provide instant notifications when verification status changes.
The SSE endpoint requires your API key. Since browsers’ EventSource API doesn’t support custom headers, you must create a backend proxy.
Proxy Timeout: Ensure your backend proxy does not impose a response time limit on the SSE connection. Some frameworks (e.g., Next.js API routes) have default timeouts that will prematurely close the stream. Use a standalone server or configure your framework to allow long-lived connections.
Backend SSE Proxy (Node.js)
Copy
Ask AI
router.get('/kyc/stream/:type/:sessionId', async (req, res) => { const { type, sessionId } = req.params; if (type !== 'individual' && type !== 'business') { return res.status(400).json({ error: 'Type must be individual or business' }); } // Validate UUID format const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (!uuidRegex.test(sessionId)) { return res.status(400).json({ error: 'Invalid session ID format' }); } // Set SSE headers res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Accel-Buffering', 'no'); const kycUrl = `https://api.identity.predicate.io/api/v1/status/realtime/${type}/${sessionId}`; try { const response = await fetch(kycUrl, { headers: { 'Authorization': `Bearer ${KYC_API_KEY}` } }); if (!response.ok) { throw new Error(`KYC API error: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); req.on('close', () => reader.cancel()); while (true) { const { done, value } = await reader.read(); if (done) break; res.write(decoder.decode(value, { stream: true })); } } catch (error) { console.error('SSE streaming error:', error); res.write(`event: error\ndata: ${JSON.stringify({ error: 'Stream failed' })}\n\n`); }});
Frontend SSE Client
Copy
Ask AI
class VerificationMonitor { constructor(sessionId, type) { this.sessionId = sessionId; this.type = type; this.eventSource = null; } start(onStatusUpdate, onError) { // Connect to YOUR backend, not directly to KYC API const url = `/kyc/stream/${this.type}/${this.sessionId}`; this.eventSource = new EventSource(url); this.eventSource.onmessage = (event) => { const status = JSON.parse(event.data); onStatusUpdate(status); if (status.status === 'completed' || status.status === 'rejected') { this.stop(); } }; this.eventSource.onerror = () => { onError('Connection lost'); this.stop(); }; } stop() { if (this.eventSource) { this.eventSource.close(); this.eventSource = null; } }}function startMonitoring(sessionId, type) { const monitor = new VerificationMonitor(sessionId, type); monitor.start( (status) => updateVerificationUI(status), (error) => console.error('Monitoring error:', error) ); return monitor;}
Verification Levels: The verify endpoint returns verified status for wallets that were verified at your organization’s current verification level. If you upgrade your verification level, previously verified wallets will need to re-verify. Wallets verified through other organizations at the same scrutiny level will also appear as verified.
Show clear progress indicators - Update the UI as session status changes
Handle all statuses - Display appropriate messages for pending, submitted, completed, rejected, and retry
Explain submitted status to users - When status is submitted, inform users that their documents are under review and they’ll be notified when a decision is made. submitted does NOT mean approved.
Provide both QR and link - Some users prefer clicking, others prefer scanning
Consider timeouts - Sessions don’t expire, but you may want to implement client-side timeouts