Integrating compliance solutions into your crypto exchange doesn't have to be complex. This comprehensive guide walks you through implementing Defy's Vera AI, Live AML, and Travel Rule APIs with real code examples, best practices, and common pitfalls to avoid.
## Getting Started
### Prerequisites
Before integration:
- Active Defy account
- API credentials (API key + secret)
- Development environment
- Testing sandbox access
- Basic understanding of REST APIs
### API Authentication
All Defy APIs use API key authentication with HMAC signatures:
```javascript
const crypto = require('crypto');
function generateSignature(apiSecret, method, path, timestamp, body = '') {
const message = `${method}${path}${timestamp}${body}`;
return crypto
.createHmac('sha256', apiSecret)
.update(message)
.digest('hex');
}
// Example usage
const apiKey = process.env.DEFY_API_KEY;
const apiSecret = process.env.DEFY_API_SECRET;
const timestamp = Date.now().toString();
const method = 'POST';
const path = '/v1/verify';
const body = JSON.stringify({ userId: '12345' });
const signature = generateSignature(apiSecret, method, path, timestamp, body);
// Add to request headers
const headers = {
'X-Defy-API-Key': apiKey,
'X-Defy-Timestamp': timestamp,
'X-Defy-Signature': signature,
'Content-Type': 'application/json'
};
```
### SDK Installation
**Node.js:**
```bash
npm install @defy/compliance-sdk
```
**Python:**
```bash
pip install defy-compliance
```
**Go:**
```bash
go get github.com/defy/compliance-go
```
**PHP:**
```bash
composer require defy/compliance-sdk
```
## Vera AI Integration (Compliance/KYB)
### Individual compliance Flow
**Step 1: Initialize Verification Session**
```javascript
const { VeraAI } = require('@defy/compliance-sdk');
const vera = new VeraAI({
apiKey: process.env.DEFY_API_KEY,
apiSecret: process.env.DEFY_API_SECRET,
environment: 'production', // or 'sandbox'
webhookUrl: 'https://your-platform.com/webhooks/vera'
});
// Create verification session
async function startVerification(userId, userInfo) {
try {
const session = await vera.createSession({
userId: userId,
verificationType: 'individual',
requiredChecks: [
'identity_document',
'proof_of_address',
'biometric',
'sanctions_screening',
'pep_screening'
],
userInfo: {
firstName: userInfo.firstName,
lastName: userInfo.lastName,
dateOfBirth: userInfo.dateOfBirth,
nationality: userInfo.nationality,
email: userInfo.email,
phone: userInfo.phone
},
metadata: {
ipAddress: userInfo.ipAddress,
userAgent: userInfo.userAgent,
signupDate: userInfo.signupDate
}
});
return {
sessionId: session.id,
redirectUrl: session.redirectUrl,
expiresAt: session.expiresAt
};
} catch (error) {
console.error('verification session creation failed:', error);
throw error;
}
}
```
**Step 2: Handle Verification Results**
```javascript
// Webhook endpoint
app.post('/webhooks/vera', async (req, res) => {
// Verify webhook signature
const isValid = vera.verifyWebhookSignature(
req.headers['x-defy-signature'],
req.body
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = req.body;
switch (event.type) {
case 'verification.completed':
await handleVerificationCompleted(event.data);
break;
case 'verification.failed':
await handleVerificationFailed(event.data);
break;
case 'verification.pending_review':
await handleVerificationPendingReview(event.data);
break;
}
res.sendStatus(200);
});
async function handleVerificationCompleted(data) {
const {
userId,
sessionId,
riskScore,
verificationLevel,
checks
} = data;
// Update user in database
await db.users.update(userId, {
verificationStatus: 'completed',
verificationLevel: verificationLevel,
riskScore: riskScore,
verificationCompletedAt: new Date(),
verificationChecks: checks
});
// Enable trading
await enableUserTrading(userId);
// Notify user
await sendEmail(userId, 'Identity Verification Successful');
}
```
**Step 3: Risk-Based Decision Making**
```javascript
async function makeVerificationDecision(userId, verificationData) {
const { riskScore, checks, flags } = verificationData;
// Auto-approve low risk
if (riskScore < 30 && allChecksPassed(checks)) {
return {
decision: 'approved',
level: 'standard',
limits: {
dailyDeposit: 50000,
dailyWithdrawal: 50000,
monthlyVolume: 1000000
}
};
}
// Manual review for medium risk
if (riskScore >= 30 && riskScore < 70) {
await queueForManualReview(userId, verificationData);
return {
decision: 'pending_review',
estimatedTime: '24 hours'
};
}
// Reject high risk
if (riskScore >= 70 || hasCriticalFlags(flags)) {
await logRejection(userId, flags);
return {
decision: 'rejected',
reason: determineRejectionReason(flags)
};
}
}
function allChecksPassed(checks) {
return checks.every(check => check.status === 'passed');
}
function hasCriticalFlags(flags) {
const criticalFlags = [
'sanctions_hit',
'pep_high_risk',
'fraudulent_document',
'stolen_identity'
];
return flags.some(flag => criticalFlags.includes(flag.type));
}
```
### Corporate KYB Flow
```javascript
async function startKYB(companyId, companyInfo) {
const session = await vera.createSession({
companyId: companyId,
verificationType: 'business',
requiredChecks: [
'company_registry',
'tax_verification',
'ubo_identification',
'authorized_signatories',
'business_license'
],
companyInfo: {
legalName: companyInfo.legalName,
registrationNumber: companyInfo.registrationNumber,
taxId: companyInfo.taxId,
incorporationCountry: companyInfo.country,
businessType: companyInfo.businessType,
website: companyInfo.website
},
ubos: companyInfo.ubos.map(ubo => ({
firstName: ubo.firstName,
lastName: ubo.lastName,
ownership: ubo.ownershipPercentage,
dateOfBirth: ubo.dateOfBirth,
nationality: ubo.nationality
}))
});
return session;
}
```
## Live AML Integration
### Transaction Monitoring
**Pre-Transaction Screening:**
```javascript
const { LiveAML } = require('@defy/compliance-sdk');
const liveAML = new LiveAML({
apiKey: process.env.DEFY_API_KEY,
apiSecret: process.env.DEFY_API_SECRET
});
async function screenTransaction(transaction) {
try {
const result = await liveAML.screenTransaction({
transactionId: transaction.id,
userId: transaction.userId,
type: transaction.type, // 'deposit', 'withdrawal', 'trade'
amount: transaction.amount,
currency: transaction.currency,
blockchain: transaction.blockchain,
addresses: {
from: transaction.fromAddress,
to: transaction.toAddress
},
metadata: {
timestamp: transaction.timestamp,
ipAddress: transaction.ipAddress,
deviceId: transaction.deviceId
}
});
return {
allowed: result.decision === 'approved',
riskScore: result.riskScore,
riskLevel: result.riskLevel,
flags: result.flags,
requiresReview: result.decision === 'manual_review'
};
} catch (error) {
// On error, default to manual review for safety
console.error('AML screening error:', error);
return {
allowed: false,
requiresReview: true,
error: error.message
};
}
}
// Usage in withdrawal flow
app.post('/api/withdraw', async (req, res) => {
const { userId, amount, currency, toAddress } = req.body;
// Create pending transaction
const transaction = await db.transactions.create({
userId,
type: 'withdrawal',
amount,
currency,
toAddress,
status: 'pending_aml_check'
});
// Screen transaction
const amlResult = await screenTransaction(transaction);
if (amlResult.allowed) {
// Approve and process
await db.transactions.update(transaction.id, {
status: 'approved',
riskScore: amlResult.riskScore
});
await processWithdrawal(transaction.id);
res.json({ success: true, transactionId: transaction.id });
} else if (amlResult.requiresReview) {
// Queue for manual review
await db.transactions.update(transaction.id, {
status: 'pending_review',
riskScore: amlResult.riskScore,
flags: amlResult.flags
});
res.json({
success: false,
status: 'pending_review',
message: 'Transaction queued for compliance review'
});
} else {
// Reject
await db.transactions.update(transaction.id, {
status: 'rejected',
rejectionReason: amlResult.flags
});
res.json({
success: false,
status: 'rejected',
message: 'Transaction rejected due to compliance reasons'
});
}
});
```
**Behavioral Analysis:**
```javascript
async function analyzeUserBehavior(userId) {
const behavior = await liveAML.analyzeBehavior({
userId: userId,
timeframe: '30d', // Last 30 days
includeMetrics: [
'transaction_frequency',
'amount_patterns',
'time_patterns',
'address_diversity',
'counterparty_risk'
]
});
return {
riskScore: behavior.overallRiskScore,
insights: {
transactionFrequency: behavior.metrics.transaction_frequency,
averageAmount: behavior.metrics.average_amount,
peakTradingHours: behavior.metrics.peak_hours,
suspiciousPatterns: behavior.flags
},
recommendations: behavior.recommendations
};
}
```
**Sanctions Screening:**
```javascript
async function screenAddress(address, blockchain) {
const result = await liveAML.screenAddress({
address: address,
blockchain: blockchain,
includeIndirectExposure: true, // Check 2-3 hops
lists: ['OFAC', 'UN', 'EU', 'MASAK'] // Which lists to check
});
if (result.hit) {
// Log sanctions hit
await logSanctionsHit({
address: address,
blockchain: blockchain,
matchedLists: result.matches,
severity: result.severity
});
// Block transaction
return {
allowed: false,
reason: 'sanctions_hit',
details: result.matches
};
}
return { allowed: true };
}
```
## Travel Rule Integration
### VASP-to-VASP Communication
**Outgoing Transfer:**
```javascript
const { TravelRule } = require('@defy/compliance-sdk');
const travelRule = new TravelRule({
apiKey: process.env.DEFY_API_KEY,
apiSecret: process.env.DEFY_API_SECRET,
vaspInfo: {
name: 'Your Exchange Name',
did: 'did:web:your-exchange.com',
jurisdiction: 'TR',
license: 'MASAK-2024-001'
}
});
async function handleOutgoingTransfer(transfer) {
// Check if amount exceeds Travel Rule threshold
const requiresTravelRule = await travelRule.checkThreshold({
amount: transfer.amount,
currency: transfer.currency,
jurisdiction: transfer.jurisdiction
});
if (!requiresTravelRule) {
// Process normally
return await processTransfer(transfer);
}
// Discover beneficiary VASP
const beneficiaryVASP = await travelRule.discoverVASP({
address: transfer.beneficiaryAddress,
blockchain: transfer.blockchain
});
if (!beneficiaryVASP) {
// Unhosted wallet - collect beneficiary info from user
const beneficiaryInfo = await requestBeneficiaryInfo(transfer.userId);
// Process with enhanced due diligence
return await processWithEDD(transfer, beneficiaryInfo);
}
// Send Travel Rule data
const trSession = await travelRule.sendData({
originator: {
name: transfer.originatorName,
address: transfer.originatorAddress,
accountNumber: transfer.userId,
vasp: {
name: 'Your Exchange',
did: 'did:web:your-exchange.com'
}
},
beneficiary: {
name: beneficiaryVASP.name,
did: beneficiaryVASP.did,
accountNumber: transfer.beneficiaryAccountId
},
transaction: {
amount: transfer.amount,
currency: transfer.currency,
blockchain: transfer.blockchain
}
});
// Wait for acceptance (with timeout)
const response = await trSession.waitForResponse({
timeout: 300000 // 5 minutes
});
if (response.status === 'accepted') {
// Link TR data to transaction
await db.transactions.update(transfer.id, {
travelRuleSessionId: trSession.id,
travelRuleStatus: 'completed'
});
// Process transaction
return await processTransfer(transfer);
} else {
// Rejected by beneficiary VASP
await db.transactions.update(transfer.id, {
status: 'rejected',
rejectionReason: response.rejectionReason
});
throw new Error(`Travel Rule rejected: ${response.rejectionReason}`);
}
}
```
**Incoming Transfer:**
```javascript
// Webhook handler for incoming Travel Rule data
app.post('/webhooks/travel-rule', async (req, res) => {
const incomingData = req.body;
// Verify originator VASP
const vaspValid = await travelRule.verifyVASP(incomingData.originator.vasp);
if (!vaspValid) {
await travelRule.rejectTransfer(incomingData.sessionId, {
reason: 'invalid_vasp',
message: 'Originator VASP could not be verified'
});
return res.sendStatus(200);
}
// Sanctions screening
const sanctionsResult = await liveAML.screenEntity({
name: incomingData.originator.name,
address: incomingData.originator.address
});
if (sanctionsResult.hit) {
await travelRule.rejectTransfer(incomingData.sessionId, {
reason: 'sanctions_hit',
message: 'Originator appears on sanctions list'
});
await fileSAR(incomingData, sanctionsResult);
return res.sendStatus(200);
}
// Risk assessment
const riskScore = await calculateIncomingRisk(incomingData);
if (riskScore < 70) {
// Auto-accept
await travelRule.acceptTransfer(incomingData.sessionId);
} else {
// Queue for manual review
await queueTransferReview(incomingData, riskScore);
}
res.sendStatus(200);
});
```
## Error Handling
### Retry Logic
```javascript
async function callAPIWithRetry(apiCall, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await apiCall();
} catch (error) {
lastError = error;
// Don't retry on client errors (4xx)
if (error.statusCode >= 400 && error.statusCode < 500) {
throw error;
}
// Exponential backoff
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
await sleep(delay);
}
}
}
throw lastError;
}
// Usage
const result = await callAPIWithRetry(() =>
vera.createSession(sessionData)
);
```
### Rate Limiting
```javascript
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', apiLimiter);
```
## Testing
### Sandbox Environment
```javascript
// Use sandbox for development
const vera = new VeraAI({
apiKey: process.env.DEFY_SANDBOX_API_KEY,
apiSecret: process.env.DEFY_SANDBOX_API_SECRET,
environment: 'sandbox'
});
// Test with mock data
const testSession = await vera.createSession({
userId: 'test_user_123',
verificationType: 'individual',
testMode: true,
testScenario: 'approved_low_risk' // or 'rejected_sanctions', 'pending_review'
});
```
### Integration Tests
```javascript
describe('Vera AI Integration', () => {
it('should create verification session successfully', async () => {
const session = await vera.createSession({
userId: 'test_123',
verificationType: 'individual'
});
expect(session).toHaveProperty('id');
expect(session).toHaveProperty('redirectUrl');
expect(session.status).toBe('pending');
});
it('should handle webhook correctly', async () => {
const webhookPayload = {
type: 'verification.completed',
data: {
userId: 'test_123',
riskScore: 25,
verificationLevel: 'standard'
}
};
const response = await request(app)
.post('/webhooks/vera')
.send(webhookPayload)
.set('X-Defy-Signature', generateTestSignature(webhookPayload));
expect(response.status).toBe(200);
});
});
```
## Best Practices
### Performance Optimization
**1. Cache API Responses:**
```javascript
const redis = require('redis');
const client = redis.createClient();
async function getCachedRiskScore(userId) {
const cached = await client.get(`risk:${userId}`);
if (cached) return JSON.parse(cached);
const riskScore = await liveAML.getRiskScore(userId);
await client.setex(`risk:${userId}`, 3600, JSON.stringify(riskScore));
return riskScore;
}
```
**2. Batch Processing:**
```javascript
// Screen multiple addresses in one request
const results = await liveAML.batchScreenAddresses({
addresses: [
{ address: '0x123...', blockchain: 'ethereum' },
{ address: '0x456...', blockchain: 'ethereum' },
{ address: 'bc1q789...', blockchain: 'bitcoin' }
]
});
```
**3. Async Processing:**
```javascript
// Don't block on non-critical checks
async function processDeposit(deposit) {
// Critical: Sanctions screening (block)
const sanctionsResult = await liveAML.screenAddress(deposit.fromAddress);
if (sanctionsResult.hit) {
throw new Error('Sanctions violation');
}
// Credit user account
await creditUserBalance(deposit);
// Non-critical: Behavioral analysis (async)
analyzeBehavior(deposit.userId).catch(err => {
console.error('Behavior analysis failed:', err);
});
return { success: true };
}
```
## Monitoring and Logging
### Logging
```javascript
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'compliance.log' })
]
});
// Log all API calls
async function loggedAPICall(apiCall, context) {
const startTime = Date.now();
try {
const result = await apiCall();
logger.info('API call successful', {
...context,
duration: Date.now() - startTime,
result: result
});
return result;
} catch (error) {
logger.error('API call failed', {
...context,
duration: Date.now() - startTime,
error: error.message,
stack: error.stack
});
throw error;
}
}
```
### Metrics
```javascript
const prometheus = require('prom-client');
// Define metrics
const verificationSessionCounter = new prometheus.Counter({
name: 'verification_sessions_total',
help: 'Total number of verification sessions created',
labelNames: ['status']
});
const amlScreeningDuration = new prometheus.Histogram({
name: 'aml_screening_duration_seconds',
help: 'AML screening duration',
buckets: [0.1, 0.5, 1, 2, 5]
});
// Record metrics
verificationSessionCounter.inc({ status: 'approved' });
const timer = amlScreeningDuration.startTimer();
await screenTransaction(tx);
timer();
```
## Conclusion
Successful integration of Defy's compliance APIs requires:
1. Proper authentication and security
2. Robust error handling
3. Comprehensive testing
4. Performance optimization
5. Monitoring and logging
**Defy Support:**
- Technical documentation: https://docs.getdefy.co
- API reference: https://api.getdefy.co/docs
- SDK examples: https://github.com/defy/examples
- Support: info@getdefy.co
- Slack community: https://defy-dev.slack.com
Start building compliant crypto platforms with Defy today!