Skip to main content
All endpoints require an API key as a Bearer Authorization header. API keys must only be used in backend server code.

Integration Steps

1

Create a Verification Session

Your backend calls the Predicate API to create a KYC or KYB session
2

Display the QR Code and Link

Show the QR code or redirect URL to the user in your application
3

Monitor Session Status

Use SSE (recommended) or polling to track verification progress
4

Verify Wallet Addresses

Check if a wallet belongs to a verified identity before transactions

Step 1: Create a Verification Session

Your backend creates a session by calling the registration endpoint. The response includes a QR code and redirect URL for the user.
cURL
curl -X POST "https://api.identity.predicate.io/api/v1/register/individual?size=300" \
  -H "Authorization: Bearer $PREDICATE_KYC_API_KEY"
Request Body (Optional):
{
  "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)
Response:
{
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "redirectUrl": "https://identity.predicate.io/session/550e8400-e29b-41d4-a716-446655440000",
  "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSU..."
}
const express = require('express');
const router = express.Router();

const KYC_API_KEY = process.env.KYC_API_KEY;

router.post('/kyc/create-session', async (req, res) => {
  const { type, walletAddress } = req.body; // 'individual' or 'business', optional wallet

  const endpoint = type === 'business' ? 'business' : 'individual';
  const url = `https://api.identity.predicate.io/api/v1/register/${endpoint}`;

  // Build request options
  const options = {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${KYC_API_KEY}`
    }
  };

  // Include wallet address if provided (restricts session to this wallet)
  if (walletAddress) {
    options.headers['Content-Type'] = 'application/json';
    options.body = JSON.stringify({ walletAddress });
  }

  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || `API error: ${response.status}`);
    }

    const data = await response.json();
    res.json(data);
  } catch (error) {
    console.error('Session creation error:', error);
    res.status(500).json({ error: 'Failed to create verification session' });
  }
});

module.exports = router;
Display the base64-encoded QR code from the response directly in an <img> tag.
<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
  • User completes identity verification
  • Session status updates are sent via SSE

Step 3: Monitor Session Status

Once a user starts verification, monitor their progress using SSE (recommended) or polling. Session Status Values:
StatusDescription
pendingSession created, user in verification process
submittedDocuments submitted, awaiting manual operator review
completedVerification successful, user is verified
rejectedVerification permanently failed (fraud, sanctions)
retryFixable 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.

Verifying Wallet Addresses

Once a user has completed verification, check if any wallet address belongs to a verified identity.
cURL
curl -X POST "https://api.identity.predicate.io/api/v1/verify" \
  -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x1234567890abcdef...",
    "type": "individual"
  }'
Response (verified):
{
  "address": "0x1234...",
  "verified": true,
  "status": "completed",
  "userId": "uuid-of-verified-identity",
  "checkType": "kyc",
  "verifiedAt": "2025-01-21T10:30:00Z",
  "wallets": [
    { "address": "0x1234...", "chain": "ethereum" },
    { "address": "0x5678...", "chain": "polygon" }
  ]
}
Response (not verified):
{
  "address": "0x1234...",
  "verified": false
}
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.

Best Practices

Security

  • Never expose API keys in frontend code - All authenticated operations must happen on your backend
  • Use environment variables - Store API keys in .env files or secure secret management systems
  • Validate session IDs - Always validate UUID format before making API calls
  • Verify session ownership - The API ensures sessions can only be accessed by the customer that created them

User Experience

  • 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

Performance

  • Use SSE over polling - Real-time updates are more efficient and user-friendly
  • Implement reconnection logic - Handle network interruptions gracefully
  • Close connections when done - Stop monitoring once verification completes or fails

Complete Integration Example

Here’s a full example tying everything together:
<!DOCTYPE html>
<html>
<head>
  <title>Identity Verification</title>
  <style>
    .kyc-container {
      max-width: 400px;
      margin: 50px auto;
      text-align: center;
      font-family: sans-serif;
    }
    .kyc-qr {
      width: 300px;
      height: 300px;
      border: 1px solid #ddd;
      margin: 20px auto;
    }
    .status-pending { color: #f59e0b; }
    .status-verified { color: #10b981; }
    .status-failed { color: #ef4444; }
  </style>
</head>
<body>
  <div class="kyc-container">
    <h1>Verify Your Identity</h1>

    <div id="start-section">
      <button id="start-btn" onclick="startVerification()">
        Start Verification
      </button>
    </div>

    <div id="verification-section" style="display: none;">
      <p>Scan the QR code or click the link below:</p>
      <img id="kyc-qr" class="kyc-qr" alt="QR Code" />
      <p><a id="kyc-link" target="_blank">Open Verification Portal →</a></p>
      <p id="verification-status" class="status-pending">Waiting...</p>
    </div>
  </div>

  <script>
    async function startVerification() {
      try {
        const response = await fetch('/kyc/create-session', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ type: 'individual' })
        });

        if (!response.ok) throw new Error('Failed to create session');

        const data = await response.json();

        document.getElementById('start-section').style.display = 'none';
        document.getElementById('verification-section').style.display = 'block';
        document.getElementById('kyc-qr').src = data.qrCode;
        document.getElementById('kyc-link').href = data.redirectUrl;

        const monitor = new VerificationMonitor(data.sessionId, 'individual');
        monitor.start(
          (status) => {
            const statusEl = document.getElementById('verification-status');
            switch (status.status) {
              case 'completed':
                statusEl.textContent = '✓ Verified!';
                statusEl.className = 'status-verified';
                break;
              case 'pending':
                statusEl.textContent = 'Verification in progress...';
                statusEl.className = 'status-pending';
                break;
              case 'submitted':
                statusEl.textContent = 'Under review...';
                statusEl.className = 'status-pending';
                break;
              case 'retry':
                statusEl.textContent = 'Please resubmit documents';
                statusEl.className = 'status-pending';
                break;
              case 'rejected':
                statusEl.textContent = '✗ Verification rejected';
                statusEl.className = 'status-failed';
                break;
            }
          },
          (error) => console.error('Monitor error:', error)
        );
      } catch (error) {
        console.error('Error:', error);
        alert('Failed to start verification');
      }
    }

    class VerificationMonitor {
      constructor(sessionId, type) {
        this.sessionId = sessionId;
        this.type = type;
        this.eventSource = null;
      }

      start(onStatusUpdate, onError) {
        const url = `/kyc/stream/${this.type}/${this.sessionId}`;
        this.eventSource = new EventSource(url);

        this.eventSource.onmessage = (event) => {
          try {
            const status = JSON.parse(event.data);
            onStatusUpdate(status);
            if (status.status === 'completed' || status.status === 'rejected') {
              this.stop();
            }
          } catch (e) {
            console.error('Parse error:', e);
          }
        };

        this.eventSource.onerror = () => {
          onError('Connection lost');
          this.stop();
        };
      }

      stop() {
        if (this.eventSource) {
          this.eventSource.close();
          this.eventSource = null;
        }
      }
    }
  </script>
</body>
</html>

API Reference

MethodEndpointPurposeAuth
POST/api/v1/register/individual[?size=N]Create individual KYC sessionAPI Key
POST/api/v1/register/business[?size=N]Create business KYB sessionAPI Key
POST/api/v1/verifyCheck if wallet is verifiedAPI Key
GET/api/v1/status/{type}/{sessionId}Get session status (one-shot)API Key
GET/api/v1/status/realtime/{type}/{sessionId}Stream session updates (SSE)API Key
Query Parameters for Registration Endpoints:
  • size (optional): QR code size in pixels, between 80 and 2000. Default: 300.
All API endpoints require a Bearer token:
Authorization: Bearer YOUR_API_KEY