VerifyNow
API Docs/Integration Guide
Developer guide

API Integration Guide

Step-by-step instructions for integrating the VerifyNow API into your application. From authentication to your first API call in minutes.

API workflow explorer

Choose an industry guide

Every guide starts with a Basic path and adds Step-up checks when the event, customer or risk level requires more evidence.

Your selection is added to the URL so this workflow can be shared.

Banking & Fintech

A customer or business record ready for account-opening rules.

View industry page

Basic

Start with the core verification

Run the minimum checks needed for the normal customer journey.

  • Standard KYC Bundle

Step-up

Ask for more when risk changes

Add these checks only when the listed trigger applies.

  • Face Match — Home AffairsRemote onboarding
  • AML, PEP & Sanctions ScreeningHigher-risk customers
  • Bank Account VerificationWhen an account will receive or send funds
  • CIPC Company SearchBusiness customers

Customer

Start in your current journey

A customer starts account opening, lending, payments or payout onboarding.

Your channel

Collect the required evidence

Identity, selfie, entity, risk and bank-account details required by your policy.

VerifyNow API

Run the selected checks

Your server sends the requests below with one idempotency key per check.

Your rules

Make the business decision

Apply your RMCP, customer-risk rating and exception rules.

Your team

Keep the audit record

Keep request IDs, returned evidence and the final business decision.

API sequence

Standard KYC Bundle

Run the Standard KYC Bundle for identity and supporting trace context.

POST /verify

Face Match — Home Affairs

Compare a live selfie with the Home Affairs identity photo for stronger remote identity binding. Submit JPG/JPEG, PNG, WEBP or TIFF image data up to 5 MB per image.

POST /facematch
Use when: Remote onboardingEndpoint details

AML, PEP & Sanctions Screening

Screen a person against AML, PEP, sanctions and adverse-risk sources when policy requires it.

POST /aml-screening
Use when: Higher-risk customersEndpoint details

Bank Account Verification

Verify the account holder and bank-account status before money moves.

POST /bank-account-verification
Use when: When an account will receive or send fundsEndpoint details

CIPC Company Search

Verify a business customer or counterparty against CIPC records.

POST /cipc
Use when: Business customersEndpoint details

Select calls according to your product and risk policy. More calls do not automatically create a compliant outcome.

Server-side orchestration example

JavaScript

const API_BASE = 'https://www.verifynow.co.za/api/external';

async function callVerifyNow(path, body, idempotencyKey) {
  const response = await fetch(`${API_BASE}${path}`, {
    method: 'POST',
    headers: {
      'x-api-key': process.env.VERIFYNOW_API_KEY,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify(body),
  });

  const data = await response.json();
  if (!response.ok) throw new Error(data.error || 'Verification failed');
  return data;
}

export async function runBankingFintechChecks(input) {
  const results = {};

  results.kyc = await callVerifyNow(
      '/verify',
      {
        bundle: 'kyc_bundle',
        idNumber: input.idNumber,
        mode: 'production',
      },
      `${input.customerReference}:kyc`
    );

  if (Boolean(input.selfieBase64)) {
    results.face = await callVerifyNow(
        '/facematch',
        {
          bundle: 'facematch',
          mode: 'production',
          selfie_image_base64: input.selfieBase64,
          id_number: input.idNumber,
        },
        `${input.customerReference}:face`
      );
  }

  if (input.riskLevel === 'high') {
    results.aml = await callVerifyNow(
        '/aml-screening',
        {
          mode: 'production',
          name: input.fullName,
          entity: 0,
          country: 'za',
          dataset: 'all',
        },
        `${input.customerReference}:aml`
      );
  }

  if (Boolean(input.bank)) {
    results.bank = await callVerifyNow(
        '/bank-account-verification',
        {
          type: 'Individual',
          firstName: input.firstName,
          surname: input.surname,
          identityNumber: input.idNumber,
          identityType: 'IDNumber',
          bankName: input.bank.name,
          bankAccountNumber: input.bank.accountNumber,
          bankBranchCode: input.bank.branchCode,
          bankAccountType: input.bank.accountType,
          mode: 'production',
        },
        `${input.customerReference}:bank`
      );
  }

  if (Boolean(input.companyRegistrationNumber)) {
    results.company = await callVerifyNow(
        '/cipc',
        {
          reportType: 'cipc_company_match',
          registration_number: input.companyRegistrationNumber,
          mode: 'production',
        },
        `${input.customerReference}:company`
      );
  }

  return {
    customerReference: input.customerReference,
    results,
    next: 'Apply your policy and route mismatches to manual review.',
  };
}

Request composition

How can I combine checks into one call?

VerifyNow does not accept an arbitrary array of checks in one public API request. Use a supported bundle where one exists. For a custom combination, expose one endpoint from your server and let that endpoint coordinate the required VerifyNow calls.

Basic ID + supplied-reference Face Match

Use bundle: "facematch_verified" on POST /facematch. It combines basic SA ID verification with a selfie-to-reference-image comparison in one VerifyNow call. It does not fetch a Home Affairs photo.

Selfie + Home Affairs photo Face Match

Use bundle: "facematch" on POST /facematch. It obtains the supported Home Affairs reference photo and compares the submitted selfie in one VerifyNow call.

Bank Account Verification cannot currently be added to either Face Match bundle payload. It remains a separate VerifyNow request, with its own idempotency key and normal component charge.

Give your application one endpoint

Your frontend or partner calls one endpoint that you control. Your server keeps the API key private, runs the appropriate Face Match bundle and Bank Verification, then returns one combined response.

One request from your client
POST /api/verification/combined
{
  "mode": "production",
  "idNumber": "8001015009087",
  "firstName": "Jane",
  "surname": "Doe",
  "selfieImageBase64": "data:image/jpeg;base64,...",
  "referenceImageBase64": "data:image/jpeg;base64,...",
  "bank": {
    "name": "Example Bank",
    "accountNumber": "1234567890",
    "branchCode": "250655",
    "accountType": "Cheque"
  }
}
Server-side orchestration
const VERIFYNOW_API = 'https://www.verifynow.co.za/api/external';

async function callVerifyNow(path, body, idempotencyKey) {
  const response = await fetch(`${VERIFYNOW_API}${path}`, {
    method: 'POST',
    headers: {
      'x-api-key': process.env.VERIFYNOW_API_KEY,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify(body),
  });

  const data = await response.json();
  if (!response.ok) throw new Error(data.error || 'Verification failed');
  return data;
}

export async function runCombinedVerification(input, requestId) {
  const useSuppliedReference = Boolean(input.referenceImageBase64);

  const identityAndFace = callVerifyNow(
    '/facematch',
    useSuppliedReference
      ? {
          bundle: 'facematch_verified',
          mode: input.mode,
          id_number: input.idNumber,
          selfie_image_base64: input.selfieImageBase64,
          reference_image_base64: input.referenceImageBase64,
        }
      : {
          bundle: 'facematch',
          mode: input.mode,
          id_number: input.idNumber,
          selfie_image_base64: input.selfieImageBase64,
        },
    `${requestId}:identity-face`
  );

  const bank = callVerifyNow(
    '/bank-account-verification',
    {
      type: 'Individual',
      firstName: input.firstName,
      surname: input.surname,
      identityNumber: input.idNumber,
      identityType: 'IDNumber',
      bankName: input.bank.name,
      bankAccountNumber: input.bank.accountNumber,
      bankBranchCode: input.bank.branchCode,
      bankAccountType: input.bank.accountType,
      mode: input.mode,
    },
    `${requestId}:bank`
  );

  const [identityFaceResult, bankResult] = await Promise.all([
    identityAndFace,
    bank,
  ]);

  return {
    requestId,
    checks: {
      identityFace: identityFaceResult,
      bank: bankResult,
    },
  };
}

The example runs both checks in parallel because both are required. If Bank Verification is a step-up check, run it only after the identity and face result passes your policy so an unnecessary second call is not made.

Get API Key

Sign up and get your API key from the Settings page.

Go to Settings

API Reference

View full API documentation with all endpoints.

View API Docs

Test Sandbox

Test with sandbox mode - uses 0 credits.

"mode": "sandbox"
1

Get Your API Key

Create an account and obtain credentials

  1. Sign up at verifynow.co.za
  2. Navigate to Settings in your account
  3. Find the API Key section
  4. Copy your API key (starts with vn_live_)

Security Warning

Keep your API key secret. Never expose it in client-side code or commit it to version control.

2

Set Up Your Environment

Configure environment variables

Store your API key as an environment variable:

# .env.local (or .env)
VERIFYNOW_API_KEY=vn_live_your_api_key_here
3

Make Your First API Call

Choose your preferred language

Optional ID number validation

For South African ID inputs, you can run the standard ID checksum in your application before sending a verification request. This optional pre-flight validation catches typing errors early and can help avoid unnecessary calls. It is not a separate API step.

Driver's Licence Verification

Send the back-of-card image or complete barcode payload to POST /drivers-licence. The response includes the decoded licence fields, validation checks, categories and embedded portrait. Choose an additive bundle when the workflow also needs an identity record, Home Affairs Face Match, current selfie comparison or Passive Liveness.

Vehicle Licence Disc Scanner

Send a disc image or decoded barcode text to POST /vehicle-licence-disc. Choose a barcode or number plate comparison report. VIN comparison requires an enabled API account. Image integrations can opt into visible-field recovery with allow_visual_fallback.

Using cURL

curl -X POST https://www.verifynow.co.za/api/external/verify \
  -H "x-api-key: $VERIFYNOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "reportType": "said_verification",
    "idNumber": "8001015009087",
    "mode": "production"
  }'

Using JavaScript/TypeScript

const response = await fetch(
  'https://www.verifynow.co.za/api/external/verify',
  {
    method: 'POST',
    headers: {
      'x-api-key': process.env.VERIFYNOW_API_KEY,
      'Content-Type': 'application/json',
      'Idempotency-Key': crypto.randomUUID(),
    },
    body: JSON.stringify({
      reportType: 'said_verification',
      idNumber: '8001015009087',
      mode: 'production',
    }),
  }
);

const result = await response.json();

Using Python

import os
import uuid
import requests

response = requests.post(
    'https://www.verifynow.co.za/api/external/verify',
    headers={
        'x-api-key': os.environ['VERIFYNOW_API_KEY'],
        'Content-Type': 'application/json',
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={
        'reportType': 'said_verification',
        'idNumber': '8001015009087',
        'mode': 'production',
    }
)

result = response.json()

Example Response

{
  "success": true,
  "requestId": "unique-request-id",
  "user_id": "11",
  "remainingCredits": 32,
  "mode": "production",
  "reportType": "said_verification",
  "input": {
    "idNumber": "9111060123086"
  },
  "results": {
    "said_verification": {
      "Status": "Success",
      "realTimeResults": {
        "Status": "ID Number Valid",
        "Verification": {
          "Firstnames": "NALEDI LERATO",
          "Lastname": "KHUMALO",
          "Dob": "1991-11-06",
          "Age": 34,
          "Gender": "Female",
          "Citizenship": "South African",
          "DateIssued": ""
        },
        "transaction_id": "19031247"
      },
      "transaction_id": "19031247",
      "meta": {
        "environment": "production",
        "timestamp": "2026-05-26T07:54:16.776Z"
      }
    }
  }
}
4

Understand Idempotency

Prevent duplicate charges on retries

Required for Production

The Idempotency-Key header is required for all production API calls. It prevents duplicate charges.

How It Works

  1. Generate a unique key (UUID) for each unique request
  2. Send this key in the Idempotency-Key header
  3. If you retry with the same key, you get the cached response (no duplicate charge)
  4. Keys expire after 30 days

What Counts as Duplicate

ScenarioResult
Same Key + Same BodyReturns cached response (no charge)
Same Key + Different Body409 Conflict error
Different Key + Same BodyNew request (charges credits)
5

Test in Sandbox Mode

Risk-free testing before going live

Before going live, test everything with sandbox mode:

{
  "mode": "sandbox"
}

Sandbox Mode Benefits

  • Uses 0 credits
  • Returns realistic mock data
  • Works with any valid-format ID number
  • Reserved 90 / 91 inputs force negative and error paths. Sandbox test triggers
  • Repeating the same sandbox payload from the same IP returns 429 after a 2-second cooldown. Distinct reserved inputs run immediately. Playground, Postman, and curl share this lock.
6

Handle Errors

Graceful error handling for production

Always handle API errors gracefully:

CodeMeaningAction
200SuccessProcess the response
400Invalid parametersCheck your request body
401Invalid API keyCheck your API key
402Insufficient creditsTop up your account
409Idempotency conflictUse a new Idempotency-Key
429Rate limitedSlow down requests
7

Go Live

Deploy to production

When ready for production:

  1. Switch to production mode:"mode": "production"
  2. Ensure you have credits - Check balance via GET /my_credits
  3. Always include Idempotency-Key for production requests
  4. Monitor your usage in the VerifyNow dashboard

Security Checklist

  • API key stored as environment variable
  • API key never exposed in client-side code
  • Idempotency-Key used for all production requests
  • Error handling implemented
  • Tested in sandbox mode first

Property Search by ID

Send a valid South African ID number to POST /property-search. Production requests need an Idempotency-Key.

A completed production search uses the current credits shown on the pricing table, including a result with found: false.

View endpoint details

Africa ID Verification

Start a job with POST /africa-verification, then fetch its result from GET /africa-verification/jobs/{jobId}.

The production POST uses the current credits shown on the pricing table. Status requests use 0 credits.

View supported countries

VerifyNow AML is a licensed ongoing AML and ODD API. Enrol, rescreen, and withdraw identified customers with name plus identifier. A dataset webhook and email notify list changes; rescreen remains your call. Set the HTTPS URL in the dashboard under API Analytics → AML Monitoring → Dataset update webhook. Credentials and the production path are issued after the licence. Read the VerifyNow AML webhook setup.

Consumer/Person Trace: All-in-One Endpoint

The Consumer/Person Trace endpoint (reportType: "consumer_trace") returns ALL of the following in a single call:

AddressData

Current and historical addresses (residential & postal)

EmploymentData

Employment history with employer names

ContactData

Phone numbers (cell and landline)

DefiniteMatchData

ID verification details

No need to make separate calls for address, employment, or phone data - it's all included.

Need Help?

For API support, contact us at support@verifynow.co.za