Skip to main content

ID Verification Guide

This guide covers identity verification using document scanning and facial recognition with the Matchstra API.

Overview

ID Verification validates government-issued identity documents and matches them against a selfie photo to confirm the person presenting the ID is the rightful owner.

Use cases:

  • Customer onboarding (KYC)
  • Age verification
  • Identity proofing for online services
  • Account recovery
  • Access control

Supported documents:

  • Passports
  • Driver's licenses
  • National ID cards
  • Residence permits

Quick Example

const formData = new FormData();
formData.append('IdCardImage', idCardFile);
formData.append('SelfieImage', selfieFile);

const response = await fetch('https://api.matchstra.ca/api/IdVerification/verify', {
method: 'POST',
headers: {
'X-API-Key': 'your_api_key_here'
},
body: formData
});

const result = await response.json();

if (result.success && result.data.faceMatch) {
console.log(`✅ Match confirmed! Confidence: ${result.data.matchConfidence}%`);
console.log(`Name: ${result.data.extractedData.fullName}`);
} else {
console.log('❌ Face does not match ID');
}

Image Requirements

ID Card Image

Format:

  • JPEG or PNG
  • Maximum size: 10 MB
  • Recommended: 1200x800 pixels or higher

Quality guidelines:

  • Clear, well-lit photo
  • All text must be legible
  • No glare or reflections
  • Document fills most of the frame
  • All four corners visible

Examples:

Good:

  • Photo taken in natural light
  • Document flat on contrasting surface
  • Sharp focus
  • No shadows or glare

Bad:

  • Blurry or out of focus
  • Poor lighting (too dark or overexposed)
  • Document at an angle
  • Covered by fingers or objects
  • Reflections obscuring text

Selfie Image

Format:

  • JPEG or PNG
  • Maximum size: 10 MB
  • Recommended: 800x800 pixels or higher

Quality guidelines:

  • Face clearly visible
  • Good lighting (front-facing)
  • Neutral expression
  • Eyes open and visible
  • No sunglasses, masks, or face coverings
  • Face fills 40-60% of frame

Examples:

Good:

  • Well-lit face
  • Looking at camera
  • No obstructions
  • Neutral or slight smile

Bad:

  • Face in shadow
  • Looking away
  • Sunglasses or hat
  • Motion blur
  • Multiple faces in frame

Making a Verification Request

Using cURL

curl -X POST https://api.matchstra.ca/api/IdVerification/verify \
-H "X-API-Key: your_api_key_here" \
-F "IdCardImage=@/path/to/drivers-license.jpg" \
-F "SelfieImage=@/path/to/selfie.jpg"

Using JavaScript (Browser)

// Get files from file input
const idCardFile = document.getElementById('idCard').files[0];
const selfieFile = document.getElementById('selfie').files[0];

const formData = new FormData();
formData.append('IdCardImage', idCardFile);
formData.append('SelfieImage', selfieFile);

const response = await fetch('https://api.matchstra.ca/api/IdVerification/verify', {
method: 'POST',
headers: {
'X-API-Key': process.env.MATCHSTRA_API_KEY
},
body: formData
});

const result = await response.json();
console.log(result);

Using C# (.NET)

using System.Net.Http;
using System.Net.Http.Headers;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key_here");

using var formData = new MultipartFormDataContent();

// Add ID card image
var idCardContent = new ByteArrayContent(File.ReadAllBytes("drivers-license.jpg"));
idCardContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/jpeg");
formData.Add(idCardContent, "IdCardImage", "drivers-license.jpg");

// Add selfie image
var selfieContent = new ByteArrayContent(File.ReadAllBytes("selfie.jpg"));
selfieContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/jpeg");
formData.Add(selfieContent, "SelfieImage", "selfie.jpg");

var response = await client.PostAsync(
"https://api.matchstra.ca/api/IdVerification/verify",
formData
);

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);

Using Python

import requests

url = 'https://api.matchstra.ca/api/IdVerification/verify'
headers = {
'X-API-Key': 'your_api_key_here'
}

files = {
'IdCardImage': open('drivers-license.jpg', 'rb'),
'SelfieImage': open('selfie.jpg', 'rb')
}

response = requests.post(url, headers=headers, files=files)
result = response.json()
print(result)

Understanding the Response

Successful Match

{
"success": true,
"message": "Verification completed successfully",
"data": {
"id": 67890,
"faceMatch": true,
"matchConfidence": 96.8,
"livenessScore": 98.5,
"status": "Verified",
"extractedData": {
"documentType": "Driver License",
"documentNumber": "D1234567",
"fullName": "JANE MARY DOE",
"dateOfBirth": "1990-08-15T00:00:00Z",
"expiryDate": "2028-08-15T00:00:00Z",
"country": "Canada",
"region": "Ontario",
"address": "123 Main St, Toronto, ON",
"nationality": "Canadian",
"sex": "F"
},
"remainingRequests": 234
},
"errors": null
}

No Match

{
"success": true,
"message": "Verification completed successfully",
"data": {
"id": 67891,
"faceMatch": false,
"matchConfidence": 42.3,
"livenessScore": 97.2,
"status": "Not Verified",
"extractedData": {
"documentType": "Driver License",
"documentNumber": "D1234567",
"fullName": "JANE MARY DOE",
"dateOfBirth": "1990-08-15T00:00:00Z",
// ... other fields
},
"remainingRequests": 233
},
"errors": null
}

Response Fields

faceMatch

Type: Boolean

true if the face in the selfie matches the face on the ID document.

Interpretation:

  • true: High confidence the person is the ID holder
  • false: Faces do not match or insufficient confidence

matchConfidence

Type: Number (0-100)

Confidence score for the face match.

Interpretation:

  • 90-100: Very high confidence
  • 80-89: High confidence
  • 70-79: Moderate confidence
  • Below 70: Low confidence (typically results in faceMatch: false)

livenessScore

Type: Number (0-100) or null

Score indicating whether the selfie is of a live person (not a photo of a photo, screen, mask, etc.).

Interpretation:

  • 90-100: Very confident it's a live person
  • 80-89: Likely a live person
  • 70-79: Uncertain (manual review recommended)
  • Below 70: Possible spoof attempt
  • null: Liveness detection not available (rare)

status

Type: String

Human-readable verification status:

  • "Verified": Face matches, liveness passed
  • "Not Verified": Face doesn't match or liveness failed
  • "Manual Review Required": Edge case requiring human review

extractedData

Type: Object

Data extracted from the ID document via OCR.

Fields:

  • documentType: Type of document ("Passport", "Driver License", "National ID", etc.)
  • documentNumber: Document/ID number
  • fullName: Full name as appears on document
  • dateOfBirth: Birth date (ISO 8601 format)
  • expiryDate: Document expiry date
  • country: Issuing country
  • region: State/province/region (if applicable)
  • address: Address on document (if present)
  • nationality: Nationality/citizenship
  • sex: Gender marker ("M", "F", "X", etc.)
note

Some fields may be null if not present on the document or not readable.

Implementation Patterns

Basic Verification Flow

async function verifyIdentity(idCardFile, selfieFile) {
const formData = new FormData();
formData.append('IdCardImage', idCardFile);
formData.append('SelfieImage', selfieFile);

const response = await fetch('https://api.matchstra.ca/api/IdVerification/verify', {
method: 'POST',
headers: {
'X-API-Key': process.env.MATCHSTRA_API_KEY
},
body: formData
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const result = await response.json();

if (!result.success) {
throw new Error(`Verification failed: ${result.message}`);
}

return result.data;
}

// Usage
try {
const verification = await verifyIdentity(idCard, selfie);

if (verification.faceMatch && verification.livenessScore > 80) {
console.log('✅ Identity verified successfully!');
console.log(`Verified: ${verification.extractedData.fullName}`);
// Proceed with account creation
} else {
console.log('❌ Verification failed');
// Prompt user to retake photos
}
} catch (error) {
console.error('Verification error:', error);
}

Multi-Criteria Decision Logic

function assessVerification(data) {
// Check face match
if (!data.faceMatch) {
return {
approved: false,
reason: 'Face does not match ID document'
};
}

// Check match confidence
if (data.matchConfidence < 75) {
return {
approved: false,
reason: 'Match confidence too low'
};
}

// Check liveness
if (data.livenessScore !== null && data.livenessScore < 70) {
return {
approved: false,
reason: 'Possible spoof attempt detected'
};
}

// Check document expiry
const expiryDate = new Date(data.extractedData.expiryDate);
if (expiryDate < new Date()) {
return {
approved: false,
reason: 'ID document has expired'
};
}

// All checks passed
return {
approved: true,
reason: 'Identity successfully verified'
};
}

// Usage
const verification = await verifyIdentity(idCard, selfie);
const decision = assessVerification(verification);

if (decision.approved) {
createAccount(verification.extractedData);
} else {
rejectApplication(decision.reason);
}

Age Verification

function verifyAge(extractedData, minimumAge = 18) {
const dob = new Date(extractedData.dateOfBirth);
const today = new Date();

let age = today.getFullYear() - dob.getFullYear();
const monthDiff = today.getMonth() - dob.getMonth();

if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) {
age--;
}

return {
age: age,
isOldEnough: age >= minimumAge,
dateOfBirth: extractedData.dateOfBirth
};
}

// Usage
const verification = await verifyIdentity(idCard, selfie);

if (verification.faceMatch) {
const ageCheck = verifyAge(verification.extractedData, 21);

if (ageCheck.isOldEnough) {
console.log(`✅ User is ${ageCheck.age} years old - access granted`);
} else {
console.log(`❌ User is only ${ageCheck.age} - access denied`);
}
}

Form Pre-filling

Use extracted data to pre-fill user registration forms:

const verification = await verifyIdentity(idCard, selfie);

if (verification.faceMatch) {
const extracted = verification.extractedData;

// Split full name
const nameParts = extracted.fullName.split(' ');
const firstName = nameParts[0];
const lastName = nameParts.slice(1).join(' ');

// Pre-fill form
document.getElementById('firstName').value = firstName;
document.getElementById('lastName').value = lastName;
document.getElementById('dob').value = extracted.dateOfBirth.split('T')[0];
document.getElementById('address').value = extracted.address || '';

// Mark as verified
document.getElementById('verificationStatus').textContent = '✅ ID Verified';
document.getElementById('verificationId').value = verification.id;
}

Retrieving Past Verifications

List All Verifications

curl -X GET https://api.matchstra.ca/api/IdVerification/list \
-H "X-API-Key: your_api_key_here"

Get Specific Verification

curl -X GET https://api.matchstra.ca/api/IdVerification/67890 \
-H "X-API-Key: your_api_key_here"

Best Practices

1. Guide Users

Provide clear instructions for capturing images:

  • Show example photos (good vs. bad)
  • Use camera preview with overlay guides
  • Give real-time feedback on image quality
  • Allow retakes before submitting

2. Optimize Image Quality

async function optimizeImage(file, maxWidth = 1200) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ratio = maxWidth / img.width;
canvas.width = maxWidth;
canvas.height = img.height * ratio;

const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

canvas.toBlob(resolve, 'image/jpeg', 0.9);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}

// Usage
const optimizedId = await optimizeImage(idCardFile);
const optimizedSelfie = await optimizeImage(selfieFile);
await verifyIdentity(optimizedId, optimizedSelfie);

3. Handle Ambiguous Results

For edge cases, implement manual review:

const verification = await verifyIdentity(idCard, selfie);

if (verification.matchConfidence >= 70 && verification.matchConfidence < 80) {
// Confidence is borderline - flag for manual review
await flagForManualReview({
verificationId: verification.id,
reason: 'Borderline match confidence',
data: verification
});

notifyUser('Your verification is under review. You will be notified within 24 hours.');
} else if (verification.matchConfidence >= 80) {
// High confidence - auto-approve
approveUser(verification);
} else {
// Low confidence - reject
rejectUser('Identity verification failed');
}

4. Privacy & Data Retention

  • Only store extracted data, not the actual images (unless required)
  • Implement data retention policies (e.g., delete after 90 days)
  • Encrypt sensitive data at rest
  • Comply with GDPR, PIPEDA, and other privacy regulations

5. Security Considerations

  • Always use HTTPS for API calls
  • Never expose API keys in client-side code
  • Validate file types and sizes before upload
  • Implement rate limiting to prevent abuse
  • Monitor for suspicious patterns (e.g., same ID with multiple selfies)

Error Handling

400 Bad Request - Invalid Images

{
"success": false,
"message": "Invalid image format or size",
"errors": [
"IdCardImage: File size exceeds 10 MB",
"SelfieImage: Unsupported image format"
]
}

Fix: Validate images before upload (format, size, dimensions).

400 Bad Request - Poor Image Quality

{
"success": false,
"message": "Image quality insufficient",
"errors": [
"ID document text not readable",
"No face detected in selfie"
]
}

Fix: Guide user to retake photos with better lighting and positioning.

429 Too Many Requests

{
"success": false,
"message": "Rate limit exceeded",
"errors": ["Request quota exhausted"]
}

Fix: Check remainingRequests and implement quota management.

Testing

Use test images in development:

// Test with sample images
const testIdCard = await fetch('/test-assets/sample-id.jpg').then(r => r.blob());
const testSelfie = await fetch('/test-assets/sample-selfie.jpg').then(r => r.blob());

const result = await verifyIdentity(testIdCard, testSelfie);
console.log('Test result:', result);

Compliance

ID verification must comply with:

  • KYC Regulations: Financial institution customer identification
  • PIPEDA (Canada): Privacy of personal information
  • GDPR (EU): Data protection and privacy
  • CCPA (California): Consumer privacy rights

Ensure you:

  • Obtain user consent before processing
  • Provide privacy notice explaining data usage
  • Allow users to access and delete their data
  • Implement appropriate security measures

Next Steps