Verifymy API Integration Guide

Overview

Welcome to the developer documentation for integrating our age assurance solution.

The Verifymy age assurance solution provides users with a range of convenient and privacy-preserving age verification and estimation methods to meet your platform's and its users' individual needs.

This developer documentation will guide you through the implementation steps for both mobile and web.

Before you starting the integration, please ensure you have:

  • API Credentials: Contact our Customer Success team to request access to the API credentials needed for integration.
  • Redirect URL: Register a valid redirect URL where users will be redirected after completing the verification process. This can be done in the "Developers" menu of your admin dashboard.

Integration Flow

A simplified view of the Verifymy integration:

1. Get Verification URL
Your app → VerifyMyAge API
2. User Verification
Redirect user to VerifyMyAge
3. Receive Code
User returns with auth code
4. Get Token
Exchange code for access token

The integration follows the standard OAuth flow:

  1. Get a verification URL from Verifymy
  2. Send your user to complete verification
  3. Receive the user back with an authorization code
  4. Exchange the code for an access token

API Integration

This section covers direct integration with the Verifymy RESTful API endpoints. Use this approach when you need maximum flexibility or are working with a programming language not supported by our SDKs.

Environments

EnvironmentBase URLPurpose
Sandboxhttps://sandbox.verifymyage.comTesting and development
Productionhttps://oauth.verifymyage.comLive data and real-world use
note

Each environment requires separate API keys. Do not use the same keys for both environments.

Authentication

All API requests must be authenticated using either:

  1. HMAC Authentication - For requests that require payload signing
  2. Basic Authentication - For token exchange

The HMAC header is required for requests with payloads to ensure data integrity.

Code
Authorization: hmac {api_key}:{hmac_signature}
Copy

Copied!

Where hmac_signature is calculated as:

Code
hmac_signature = HMAC-SHA256(request_body, api_secret)
Copy

Copied!


$api_key = 'YOUR_API_KEY';
$api_secret = 'YOUR_API_SECRET';
$request_body = json_encode($payload);
$hmac_signature = hash_hmac('sha256', $request_body, $api_secret);
$hmac_header_value = 'hmac ' . $api_key . ':' . $hmac_signature;

// Add to your request headers
$headers = [
    'Authorization' => $hmac_header_value,
    'Content-Type' => 'application/json'
];

Copy

Copied!


const crypto = require('crypto');

const apiKey = 'YOUR_API_KEY';
const apiSecret = 'YOUR_API_SECRET';
const requestBody = JSON.stringify(payload);
const hmacSignature = crypto.createHmac('sha256', apiSecret)
                     .update(requestBody)
                     .digest('hex');
const hmacHeaderValue = "hmac " + apiKey + ":" + hmacSignature;

// Add to your request headers
const headers = {
    'Authorization': hmacHeaderValue,
    'Content-Type': 'application/json'
};

Copy

Copied!


import hmac
import hashlib
import json

api_key = 'YOUR_API_KEY'
api_secret = 'YOUR_API_SECRET'
request_body = json.dumps(payload)

def generate_auth_hmac_header(api_key, api_secret, request_body):
    sign_bytes = hmac.new(api_secret.encode('utf-8'), request_body.encode('utf-8'), hashlib.sha256).digest()
    sign_hex = sign_bytes.hex()
    authorization_header = f"hmac {api_key}:{sign_hex}"
    return authorization_header

headers = {
    'Authorization': generate_auth_hmac_header(api_key, api_secret, request_body),
    'Content-Type': 'application/json'
}

Copy

Copied!


import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
)

func generateAuthHmacHeader(apiKey, apiSecret string, payload interface{}) (string, error) {
    requestBody, err := json.Marshal(payload)
    if err != nil {
        return "", err
    }
    
    h := hmac.New(sha256.New, []byte(apiSecret))
    h.Write(requestBody)
    
    signBytes := h.Sum(nil)
    signHex := hex.EncodeToString(signBytes)
    authorizationHeader := fmt.Sprintf("hmac %s:%s", apiKey, signHex)
    
    return authorizationHeader, nil
}

// Use in your request
authHeader, _ := generateAuthHmacHeader(apiKey, apiSecret, payload)
headers := map[string]string{
    "Authorization": authHeader,
    "Content-Type": "application/json",
}

Copy

Copied!

Basic Authentication

For token exchange, use Basic Authentication with your API key and secret:

Code
Authorization: Basic {base64_encoded_credentials}
Copy

Copied!

Where base64_encoded_credentials is the Base64 encoding of {api_key}:{api_secret}.

Code

$headers = [
    'Authorization' => 'Basic ' . base64_encode(
        $apiKey . ':' . $apiSecret
    )
];           

Copy

Copied!

Verification Flow

info

The verification process consists of 3 steps:

  1. Generate a verification URL
  2. Exchange code for access token
  3. Check verification status

Step 1: Generate Verification URL

This endpoint generates the URL to redirect your user to and starts the verification flow. The stealth parameter should be included as a query parameter, while all other parameters should be in the request body.

HTTP

POST /v2/auth/start
Content-Type: application/json
Authorization: hmac {api_key}:{hmac_signature}

{
    "redirect_url": "https://your-website.com/callback",
    "country": "gb"
}        

Copy

Copied!

Query Parameters

ParameterRequiredDescription
stealthNoWhen set to true, attempts to verify the user in the background without redirecting them.

Request Body Parameters

ParameterRequiredDescription
redirect_urlYesURL that the user will be redirected to after verification
countryYes2-letter ISO country code (gb, de, fr, us)
methodNoDirects the user to try to verify using this method (more information below)
business_settings_idNoID containing your account configuration including the verification methods which will be made available to your users
external_user_idNoYour system's UUID to identify the user
verification_idNoUUID to identify a verification
user_infoNoContains the users encrypted email address or mobile number if you wish to perform a background age check (more information below)

Method Parameter

Using the method parameter will direct the user to verify using the method you specify first.

If this parameter isn’t used, the user will be presented with a list of verification methods you have made available to them.

You can use the method parameter with one of the following values:

Method NameDescription
AgeEstimationAn age estimation method based on a person’s facial features captured in a short selfie video.
EmailAn age estimation method that uses only an email address to accurately determine user age while preserving privacy.
IDScanAn age verification method that uses ID scanning to determine a user’s exact age.
IDScanFaceMatchAn age verification method that uses government-issued IDs to determine a user's age and employs face match technology to verify ID ownership.
MobileAn age verification method confirming the phone is in the possession of and authorised for use by a person aged 18+.
CreditCardAn age verification method using credit card authorisation to confirm age.
DoubleBlindThe user uploads a scan of their ID and a selfie to match them to their ID via a 3rd party to ensure complete anonymity.

Example Request with Specific Verification Method

This example shows how to request a specific verification method instead of relying on the default method for a country:

HTTP

POST /v2/auth/start
Content-Type: application/json
Authorization: hmac {api_key}:{hmac_signature}

{
    "redirect_url": "https://your-website.com/callback",
    "method": "IDScanFaceMatch",
    "external_user_id": "user-12345"
}

Copy

Copied!

info

Tip

You can specify a method directly without providing a country code. This is useful for global applications where you want to use the same verification method regardless of the user's location.

User Info Parameter

Here you can utilise user data collected during the account creation process or other interactions on your site to perform a background age check. If using the user_info parameter, you must set stealth to true and provide at least the user's email address or mobile number.

JSON

{
  "user_info": {
    "email": "encrypted_email_address",
    "phone": "encrypted_phone_number"
  }
}

Copy

Copied!

When using the user_info parameter, you should be prepared to handle two different response formats:

When the background age check succeeds:

JSON

{
  "verification_id": "682248a4c026ee49c709bf09",
  "verification_status": "approved"
}

Copy

Copied!

When the background age check fails or isn't available:

JSON

{
  "start_verification_url": "https://sandbox.verifymyage.com/start/verification/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "verification_id": "682248a4c026ee49c709bf09",
  "verification_status": "started"
}

Copy

Copied!

User Information Encryption Example

This example demonstrates how to encrypt user information sent in the user_info parameter.

Encryption Examples

clientSecret, true), 0, 32);
    $iv                 = openssl_random_pseudo_bytes(16);
    $ciphertext         = openssl_encrypt($text, 'AES-256-CFB', $secretHash, OPENSSL_RAW_DATA, $iv);
    $ciphertext_base64  = base64_encode($iv . $ciphertext);    
    return $ciphertext_base64;
}

$clientSecret = 'your_api_secret_key';

// User information to encrypt
$email = '[email protected]';
$phone = '+44111111111';

// Encrypt user information
$encryptedEmail = $this->encrypt($email);
$encryptedPhone = $this->encrypt($phone);

// Create payload with encrypted data
$payload = [
    'user_info' => [
        'email' => $encryptedEmail,
        'phone' => $encryptedPhone
    ],
    'redirect_url' => 'https://your-website.com/callback',
    'country' => 'gb',
];

// JSON encode for the request
echo json_encode($payload, JSON_PRETTY_PRINT);

// API endpoint with stealth parameter in query string
$endpoint = '/v2/auth/start?stealth=true';
?>

Copy

Copied!


const crypto = require('crypto');

function encryptText(keyStr, text) {
    const hash = crypto.createHash('sha256');
    hash.update(keyStr);
    const keyBytes = hash.digest();  
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv('aes-256-cfb', keyBytes, iv);
    let enc = [iv, cipher.update(text, 'utf8')];
    enc.push(cipher.final());
    return Buffer.concat(enc).toString('base64');
}

// Full example usage:
const crypto = require('crypto');
const clientSecret = 'your_api_secret_key';

// User information to encrypt
const email = '[email protected]';
const phone = '+44111111111';

// Encrypt user information
const encryptedEmail = encryptText(clientSecret, email);
const encryptedPhone = encryptText(clientSecret, phone);

// Prepare request payload
const payload = {
    user_info: {
        email: encryptedEmail,
        phone: encryptedPhone
    },
    redirect_url: 'https://your-website.com/callback',
    country: 'gb'
};

// JSON encode for the request
const jsonPayload = JSON.stringify(payload, null, 2);
console.log(jsonPayload);

// API endpoint with stealth parameter in query string
const endpoint = '/v2/auth/start?stealth=true';

Copy

Copied!


import base64
import hashlib
import hashlib, hmac, base64, os
import json
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

def encrypt_aes(secret: str, data: str) -> str:
    key = hashlib.sha256(secret.encode()).digest()
    plain_text = data.encode()
    iv = os.urandom(16)
    cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    cipher_text = encryptor.update(plain_text) + encryptor.finalize()
    iv_cipher_text = iv + cipher_text
    encrypted_data = base64.b64encode(iv_cipher_text).decode('utf-8')
    return encrypted_data

# Full example usage:
client_secret = 'your_api_secret_key'

# User information to encrypt
email = '[email protected]'
phone = '+44111111111'

# Encrypt user information
encrypted_email = encrypt_aes(client_secret, email)
encrypted_phone = encrypt_aes(client_secret, phone)

# Prepare request payload
payload = {
    'user_info': {
        'email': encrypted_email,
        'phone': encrypted_phone
    },
    'redirect_url': 'https://your-website.com/callback',
    'country': 'gb'
}

# JSON encode for the request
json_payload = json.dumps(payload, indent=2)
print(json_payload)

# API endpoint with stealth parameter in query string
endpoint = '/v2/auth/start?stealth=true'

Copy

Copied!

note

This Python example requires the cryptography library. Install it with:

pip install cryptography
note

Ensure you're using the correct API secret from the appropriate environment (sandbox or production). Using the wrong secret will result in encryption that cannot be decrypted by the Verifymy service.

Error Responses

Status CodeMessageDescription
400{"message": "invalid request", "status_code": 400}The request format is incorrect
401{"message": "unauthorized", "status_code": 401}Invalid API key or HMAC
500{"message": "Internal Server Error", "status_code": 500}Server error

Step 2: Exchange Code for Access Token

After the user completes their verification they will be redirected to your redirect_url. A code query parameter is added to this url which you will use in this step to request an access token to retrieve the users verification status in the next step.

HTTP

POST /oauth/token
Content-Type: application/json
Authorization: Basic {base64_encoded_credentials}

{
    "code": "CODE-RECEIVED-IN-REDIRECT"
}

Copy

Copied!

Request Parameters

ParameterRequiredDescription
codeYesThe code received in the redirect

Sample Response

JSON

{
  "access_token": "ACCESS-TOKEN-VALUE"
}

Copy

Copied!

Error Responses

Status CodeMessageDescription
401{"message": "invalid client credentials", "status_code": 401}Invalid credentials
401{"message": "Unauthorized redirect_url", "status_code": 401}Redirect URL not allowed
500{"message": "Internal Server Error", "status_code": 500}Server error

Step 3: Check Verification Status

Using the access token you received in the previous step you can now retrieve the user's verification status.

HTTP

GET /users/me?access_token={ACCESS-TOKEN-VALUE}

Copy

Copied!

Response Parameters

ParameterDescription
age_verifiedBoolean indicating verification status which will be “true” for a successful verification, or “false” for an unsuccessful verification
idUnique identifier for the verification
thresholdThe minimum age your users must be to successfully pass verification (default is set to 18)

Sample Response

JSON

{
  "age_verified": true,
  "id": "ABC-12345-DEF-64321",
  "threshold": 18
}

Copy

Copied!

Error Responses

Status CodeMessageDescription
400{"message": "malformed token", "status_code": 400}Invalid token format
500{"message": "error trying to save verification", "status_code": 500}Database error
500{"message": "Internal Server Error", "status_code": 500}Server error

SDK Integration

This section covers integration with Verifymy using our dedicated Software Development Kits (SDKs), which provide pre-built components and simplified methods for different programming languages.

Available SDKs

We provide SDKs for multiple languages to simplify integration:

PHP SDK

SDK Installation Instructions

PHP

Install via Composer
composer require verifymyage/oauth
Copy

Copied!

composer.json

{
    "require": {
        "verifymyage/oauth": "^1.0"
    }
}    

Copy

Copied!

info

Requirements:

  • PHP 7.4 or higher
  • Composer
  • PHP cURL extension

PHP SDK Example

PHP

<?php

use VerifyMyAgeOAuthV2;
use VerifyMyAgeCountries;
use VerifyMyAgeMethods;

// Initialize the OAuth client
$oauth = new OAuthV2(
    clientID: 'your-client-id',
    clientSecret: 'your-client-secret',
    redirectURL: 'https://your-app.com/callback'
);

// Use sandbox for development
$oauth->useSandbox();

// Step 1: Generate verification URL
$verificationUrl = $oauth->getVerificationUrl([
    'country' => Countries::GB,
    'redirect_url' => 'https://your-website.com/callback'
]);

// Step 2: Exchange code for token (in your callback endpoint)
$accessToken = $oauth->exchangeCodeByToken($_GET['code']);

// Step 3: Check verification status
$user = $oauth->user($accessToken);
if ($user['age_verified']) {
    // User is verified, allow access
} else {
    // User is not verified, deny access
}

Copy

Copied!

Verifymy on Native Mobile Device

This guide explains how to integrate the Verifymy verification flow within a WebView for iOS and Android applications.

Prerequisites

  • Verifymy API credentials:
    • API Key
    • Secret Key
  • Your application's callback URL is registered with Verifymy

General Requirements

  1. WebView must have JavaScript enabled
  2. Camera permissions must be requested and granted for ID verification
  3. Local Storage must be enabled
  4. HTTPS connections must be allowed
  5. Redirect handling must be implemented for the callback URL

Example Implementation

info

Complete Examples Available:

  • Android:

    Download Android Example - Kotlin implementation with WebView integration, camera handling, and verification logic.

  • iOS:

    Download iOS Example - Swift implementation with WKWebView integration and verification support.

Implementation Checklist

Use this checklist to ensure you've completed all necessary steps for a successful integration.

Android Implementation

Setup

  1. Add your API credentials in Constants.kt:

Kotlin

const val API_KEY = "your_api_key"
const val API_SECRET_KEY = "your_api_secret"

Copy

Copied!

  1. Configure your callback URL:

Kotlin

const val CALLBACK_URL = "your-app://callback"

Copy

Copied!

AndroidManifest.xml

XML

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />

<!-- For callback URL handling -->
<data
    android:scheme="your-app"
    android:host="callback" />

Copy

Copied!

WebView Configuration

Kotlin

webView.settings.apply {
    javaScriptEnabled = true
    domStorageEnabled = true
    mediaPlaybackRequiresUserGesture = false  // Important for camera access
    databaseEnabled = true
}

// Handle callbacks
webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
        val url = request.url.toString()
        if (url.startsWith(CALLBACK_URL)) {
            // Extract verification code
            val uri = Uri.parse(url)
            val code = uri.getQueryParameter("code")
            if (code != null) {
                // Call status verification API
                verifyStatus(code)
            }
            return true
        }
        return false
    }
}

Copy

Copied!

iOS Implementation

Setup

Add to Info.plist:

XML

NSCameraUsageDescription
Camera access is required for ID verification

NSMicrophoneUsageDescription
Microphone access may be required for video verification

Copy

Copied!

URL Handling

Swift

extension ViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView,
                 decidePolicyFor navigationAction: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let url = navigationAction.request.url,
           url.absoluteString.starts(with: YOUR_CALLBACK_URL) {
            // Handle callback URL
            decisionHandler(.cancel)
            return
        }
        decisionHandler(.allow)
    }
}

Copy

Copied!

Best Practices

1. Error Handling

  • Implement proper error handling for network issues
  • Show user-friendly error messages
  • Provide retry options when appropriate

2. Security

  • Use HTTPS for all network communications
  • Never store API credentials in client-side code
  • Implement proper HMAC signing for API requests
  • Validate callback parameters

3. User Experience

  • Show loading indicators during verification
  • Handle device rotation appropriately
  • Implement proper back button handling
  • Provide clear feedback on camera permissions

4. Testing

  • Test with different device types and OS versions
  • Verify camera functionality
  • Test poor network conditions
  • Verify callback handling

Common Issues

1. Camera Not Working

  • Ensure proper permissions are requested
  • Verify WebView settings allow camera access
  • Check for HTTPS connection

2. Callback Not Received

  • Verify callback URL is registered correctly
  • Check URL handling implementation
  • Verify network connectivity

3. Session Issues

  • Ensure cookies are enabled
  • Verify proper handling of redirects
  • Check for proper CORS settings

4. Console Warnings

The following warnings may appear in the console:

Log
[Violation] Permissions policy violation: accelerometer is not allowed in this document
Copy

Copied!

Log
The deviceorientation events are blocked by permissions policy. See https://github.com/w3c/webappsec-permissions-policy/blob/master/features.md#sensor-features
Copy

Copied!

Log
The devicemotion events are blocked by permissions policy. See https://github.com/w3c/webappsec-permissions-policy/blob/master/features.md#sensor-features
Copy

Copied!

These warnings can be safely ignored. They do not affect the verification process since they are related to a third-party library.

Troubleshooting

This section provides solutions to common integration issues and helpful debugging tips.

Common Issues

HMAC Authentication Failures

Ensure your API key and secret are correct

Verify that you're using the exact request body for HMAC calculation

Check that your HMAC header format is correct: hmac {api_key}:{hmac_signature}

info

Tip:

If you're having trouble with HMAC authentication, try using our SDK which handles HMAC calculation for you.

Redirect URL Unauthorized

  • Make sure the redirect_url is registered in your allowed redirect URLs
  • Check for exact matching including protocol (http/https) and trailing slashes
note

Common mistake:

https://example.com/callback and https://example.com/callback/ are considered different URLs. Register both if needed.

Verification Flow Interruptions

  • Ensure your redirect_url endpoint is properly handling the code parameter
  • Verify that your server can make outbound HTTPS requests
  • Check that your server's IP is not blocked by any firewall
info

Testing tip:

Use our Postman collection to verify each step of the flow independently.

API Error Codes

Common error status codes and their resolutions:

StatusErrorResolution
400Bad RequestCheck that your request format and parameters are correct
401UnauthorizedVerify API credentials and HMAC signature
403ForbiddenYour credentials are valid but you lack permission for this action
429Too Many RequestsImplement rate limiting in your application
500Server ErrorContact support with your request details and timestamp