Legacy Verification Modules
Legacy API: The modules system is still supported but deprecated in favor of the more powerful flow-based approach. New integrations should use Custom Flows for better flexibility and conditional logic.
Available node types
FaceSign provides several node types that you can combine in your flows:
Node types
Each node has a specific type
identifier that you use when creating flows:
- Name
start
- Type
- node
- Description
Entry point node - every flow must begin with this
- Name
end
- Type
- node
- Description
Terminal node - marks successful completion of the flow
- Name
conversation
- Type
- node
- Description
AI-powered conversational interaction node
- Name
email_verification
- Type
- node
- Description
Email OTP verification node
- Name
sms_verification
- Type
- node
- Description
SMS OTP verification node
- Name
liveness_detection
- Type
- node
- Description
Biometric liveness verification node
- Name
document_scan
- Type
- node
- Description
Government document scanning and authentication node
- Name
age_estimation
- Type
- node
- Description
AI-powered age verification and estimation node
Creating flows
Basic flow configuration
Create flows by defining nodes and edges that connect them:
Basic flow setup
const { session, clientSecret } = await client.sessions.create({
clientReferenceId: 'user_123',
flow: {
nodes: [
{ id: 'start', type: 'start' },
{ id: 'email', type: 'email_verification' },
{ id: 'liveness', type: 'liveness_detection' },
{ id: 'document', type: 'document_scan' },
{ id: 'end', type: 'end' }
],
edges: [
{ id: 'e1', source: 'start', target: 'email' },
{ id: 'e2', source: 'email', target: 'liveness' },
{ id: 'e3', source: 'liveness', target: 'document' },
{ id: 'e4', source: 'document', target: 'end' }
]
}
});
Advanced flow configuration
Nodes can accept configuration options and define conditional outcomes:
Advanced flow configuration
const { session, clientSecret } = await client.sessions.create({
clientReferenceId: 'user_123',
flow: {
nodes: [
{ id: 'start', type: 'start' },
{
id: 'email',
type: 'email_verification',
config: {
email: 'user@example.com', // Pre-fill email
name: 'John Doe' // Personalize email
}
},
{
id: 'liveness',
type: 'liveness_detection',
outcomes: {
livenessDetected: 'document',
deepfakeDetected: 'end_failure'
}
},
{
id: 'document',
type: 'document_scan',
outcomes: {
documentVerified: 'end_success',
documentRejected: 'end_failure'
}
},
{ id: 'end_success', type: 'end' },
{ id: 'end_failure', type: 'end' }
],
edges: [
{ id: 'e1', source: 'start', target: 'email' },
{ id: 'e2', source: 'email', target: 'liveness' },
{ id: 'e3', source: 'liveness', target: 'document', condition: 'livenessDetected' },
{ id: 'e4', source: 'liveness', target: 'end_failure', condition: 'deepfakeDetected' },
{ id: 'e5', source: 'document', target: 'end_success', condition: 'documentVerified' },
{ id: 'e6', source: 'document', target: 'end_failure', condition: 'documentRejected' }
]
}
});
Common module combinations
Basic identity verification
Perfect for user onboarding and account verification:
Basic identity flow
const basicIdentityModules = [
{ type: 'emailVerification' },
{ type: 'identityVerification' }
];
Financial services KYC
Comprehensive verification for financial applications:
Financial KYC flow
const kycModules = [
{ type: 'emailVerification' },
{ type: 'smsVerification' },
{ type: 'identityVerification' },
{ type: 'documentAuthentication' },
{
type: 'proofOfIntent',
requestedData: [
{
key: 'kyc_consent',
isRequired: true,
description: 'I consent to identity verification as required by law'
}
]
}
];
Age-restricted services
For platforms requiring age verification:
Age verification flow
const ageVerificationModules = [
{ type: 'emailVerification' },
{
type: 'ageEstimation',
age: 21 // Must be 21 or older
},
{ type: 'documentAuthentication' }, // Backup verification
{
type: 'proofOfIntent',
requestedData: [
{
key: 'age_verification_consent',
isRequired: true,
description: 'I confirm that I am of legal age to use this service'
}
]
}
];
High-security applications
Maximum security for sensitive applications:
High-security flow
const highSecurityModules = [
{ type: 'emailVerification' },
{ type: 'smsVerification' },
{ type: 'identityVerification' },
{ type: 'documentAuthentication' },
{ type: 'knowledgeVerify' },
{
type: 'proofOfIntent',
requestedData: [
{
key: 'security_acknowledgment',
isRequired: true,
description: 'I understand this is a high-security verification process'
}
]
}
];
Quick social verification
Lightweight verification for social platforms:
Social platform flow
const socialVerificationModules = [
{ type: 'emailVerification' },
{
type: 'ageEstimation',
age: 13 // COPPA compliance
}
];
Dynamic module selection
User-based module selection
Choose modules based on user characteristics:
Dynamic module selection
async function selectModulesForUser(userId: string, verificationLevel: 'basic' | 'standard' | 'premium') {
const user = await db.users.findUnique({ where: { id: userId } });
const baseModules = [{ type: 'emailVerification' }];
switch (verificationLevel) {
case 'basic':
return baseModules;
case 'standard':
return [
...baseModules,
{ type: 'identityVerification' }
];
case 'premium':
return [
...baseModules,
{ type: 'smsVerification' },
{ type: 'identityVerification' },
{ type: 'documentAuthentication' }
];
}
}
async function createAdaptiveSession(userId: string, verificationLevel: string) {
const modules = await selectModulesForUser(userId, verificationLevel);
return client.sessions.create({
clientReferenceId: userId,
modules,
metadata: {
verificationLevel,
userId
}
});
}
Risk-based module selection
Adjust verification requirements based on risk assessment:
Risk-based verification
interface RiskAssessment {
score: number; // 0-100, higher = more risky
factors: string[];
recommendation: 'low' | 'medium' | 'high';
}
async function assessUserRisk(userId: string, ipAddress: string): Promise<RiskAssessment> {
// Implement your risk assessment logic
const factors = [];
let score = 0;
// Check if new user
const user = await db.users.findUnique({ where: { id: userId } });
if (!user.emailVerified) {
score += 20;
factors.push('unverified_email');
}
// Check IP reputation
const ipRisk = await checkIPReputation(ipAddress);
if (ipRisk.isVPN || ipRisk.isTor) {
score += 30;
factors.push('suspicious_ip');
}
// Check device fingerprint
const deviceRisk = await checkDeviceRisk(userId);
if (deviceRisk.isNewDevice) {
score += 15;
factors.push('new_device');
}
let recommendation: 'low' | 'medium' | 'high';
if (score < 25) recommendation = 'low';
else if (score < 60) recommendation = 'medium';
else recommendation = 'high';
return { score, factors, recommendation };
}
async function createRiskBasedSession(userId: string, ipAddress: string) {
const riskAssessment = await assessUserRisk(userId, ipAddress);
let modules;
switch (riskAssessment.recommendation) {
case 'low':
modules = [
{ type: 'emailVerification' }
];
break;
case 'medium':
modules = [
{ type: 'emailVerification' },
{ type: 'identityVerification' }
];
break;
case 'high':
modules = [
{ type: 'emailVerification' },
{ type: 'smsVerification' },
{ type: 'identityVerification' },
{ type: 'documentAuthentication' },
{ type: 'knowledgeVerify' }
];
break;
}
return client.sessions.create({
clientReferenceId: userId,
modules,
metadata: {
riskScore: riskAssessment.score,
riskFactors: riskAssessment.factors,
ipAddress
}
});
}
Module sequencing
Sequential vs. parallel verification
FaceSign automatically handles module sequencing for optimal user experience:
Module flow patterns
// Sequential flow - one module at a time
const sequentialModules = [
{ type: 'emailVerification' }, // Step 1: Verify email first
{ type: 'smsVerification' }, // Step 2: Then SMS
{ type: 'identityVerification' } // Step 3: Finally biometric
];
// Parallel flow - multiple modules can be completed in any order
const flexibleModules = [
{ type: 'emailVerification' },
{ type: 'smsVerification' },
// These can be completed in any order
];
Conditional module activation
Use custom flows to activate modules based on previous results:
Conditional module flows
// Example: Only require document verification if biometric confidence is low
const conditionalFlow = {
nodes: [
{
id: 'email_verification',
type: 'email',
outcomes: { success: 'identity_check', failure: 'end_failure' }
},
{
id: 'identity_check',
type: 'identity',
outcomes: {
high_confidence: 'end_success',
low_confidence: 'document_verification',
failure: 'end_failure'
}
},
{
id: 'document_verification',
type: 'document',
outcomes: { success: 'end_success', failure: 'end_failure' }
}
],
edges: [
{ source: 'email_verification', target: 'identity_check', condition: 'success' },
{ source: 'identity_check', target: 'document_verification', condition: 'low_confidence' },
// ... other edges
]
};
const { session, clientSecret } = await client.sessions.create({
clientReferenceId: 'user_123',
flow: conditionalFlow, // Use custom flow instead of simple modules array
modules: [] // Empty when using custom flow
});
Module results and data
Accessing verification results
Each module provides specific data in the session report:
Module result handling
// Handle webhook with module-specific results
app.post('/webhooks/facesign', async (req, res) => {
const { type, data } = req.body;
if (type === 'session.completed') {
const { session } = data;
const { report } = session;
// Check overall verification status
if (report.isVerified) {
// Extract data from different modules
const extractedData = report.extractedData || {};
// Email verification results
if (extractedData.email) {
await updateUserEmail(session.settings.clientReferenceId, extractedData.email);
}
// SMS verification results
if (extractedData.phone) {
await updateUserPhone(session.settings.clientReferenceId, extractedData.phone);
}
// Document authentication results
if (extractedData.firstName && extractedData.lastName) {
await updateUserName(
session.settings.clientReferenceId,
extractedData.firstName,
extractedData.lastName
);
}
// Age estimation results
if (report.aiAnalysis?.ageMin && report.aiAnalysis?.ageMax) {
await updateUserAgeRange(
session.settings.clientReferenceId,
report.aiAnalysis.ageMin,
report.aiAnalysis.ageMax
);
}
// Proof of intent results
if (extractedData.terms_acceptance) {
await recordUserConsent(
session.settings.clientReferenceId,
'terms_of_service',
extractedData.terms_acceptance === 'true'
);
}
}
}
res.status(200).send('OK');
});
Best practices
1. Start simple, add complexity gradually
Begin with basic modules and add more based on your needs:
Progressive module adoption
// Phase 1: Basic email verification
const phase1Modules = [{ type: 'emailVerification' }];
// Phase 2: Add biometric verification
const phase2Modules = [
{ type: 'emailVerification' },
{ type: 'identityVerification' }
];
// Phase 3: Full verification suite
const phase3Modules = [
{ type: 'emailVerification' },
{ type: 'smsVerification' },
{ type: 'identityVerification' },
{ type: 'documentAuthentication' }
];
2. Consider user experience
Balance security with user experience:
UX-optimized module selection
// Good: Progressive verification
const userFriendlyFlow = [
{ type: 'emailVerification' }, // Quick and familiar
{ type: 'identityVerification' } // Fast biometric check
];
// Avoid: Too many verification steps at once
const overwhelmingFlow = [
{ type: 'emailVerification' },
{ type: 'smsVerification' },
{ type: 'identityVerification' },
{ type: 'documentAuthentication' },
{ type: 'knowledgeVerify' },
{ type: 'ageEstimation' }
]; // This may cause user abandonment
3. Match modules to use case
Choose modules that make sense for your application:
Use case specific modules
const USE_CASE_MODULES = {
social_media: [
{ type: 'emailVerification' },
{ type: 'ageEstimation', age: 13 }
],
dating_app: [
{ type: 'emailVerification' },
{ type: 'identityVerification' }, // Prevent catfishing
{ type: 'ageEstimation', age: 18 }
],
financial_services: [
{ type: 'emailVerification' },
{ type: 'smsVerification' },
{ type: 'identityVerification' },
{ type: 'documentAuthentication' },
{ type: 'proofOfIntent' }
],
gaming: [
{ type: 'emailVerification' },
{ type: 'ageEstimation', age: 13 }
]
};
4. Handle module failures gracefully
Provide fallback options when modules fail:
Module fallback strategies
// If biometric verification fails, fall back to document verification
const fallbackFlow = {
primary: [
{ type: 'emailVerification' },
{ type: 'identityVerification' }
],
fallback: [
{ type: 'emailVerification' },
{ type: 'documentAuthentication' }, // Alternative to biometric
{ type: 'knowledgeVerify' } // Additional security
]
};
Next steps
Explore individual module documentation for detailed configuration options:
- Email Verification - Configure email OTP verification
- SMS Verification - Set up SMS-based verification
- Identity Verification - Biometric verification setup
- Document Authentication - Government document verification
- Age Estimation - AI-powered age verification
- Proof of Intent - User consent and intent capture
- Knowledge Verify - Knowledge-based authentication