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 holderfalse: 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 numberfullName: Full name as appears on documentdateOfBirth: Birth date (ISO 8601 format)expiryDate: Document expiry datecountry: Issuing countryregion: State/province/region (if applicable)address: Address on document (if present)nationality: Nationality/citizenshipsex: Gender marker ("M", "F", "X", etc.)
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
- Review the API Reference for complete endpoint details
- Learn about Error Handling for robust error management
- Check Rate Limits to plan your usage
- Try the API Explorer to test with sample images