Skip to content

VoP Security Best Practices

// ✅ Correct - Always use HTTPS
const response = await fetch('https://your-vop-service.com/api/v1/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ iban, name })
});
// ❌ Wrong - Never use HTTP for payment data
const response = await fetch('http://your-vop-service.com/api/v1/verify', {
// This exposes payment data!
});
Terminal window
# ✅ Correct - Always use client certificates
curl -X POST https://your-vop-service.com/api/v1/verify \
--cert client.crt --key client.key \
-H "Content-Type: application/json" \
-d '{"iban":"DE89370400440532013000","name":"John Smith"}'
# ❌ Wrong - Never skip certificate authentication
curl -X POST https://your-vop-service.com/api/v1/verify \
-H "Content-Type: application/json" \
-d '{"iban":"DE89370400440532013000","name":"John Smith"}'
// ✅ Correct - Validate and sanitize input
function verifyPayee(iban, name) {
// Validate IBAN format
if (!isValidIBAN(iban)) {
throw new Error('Invalid IBAN format');
}
// Sanitize name input
const sanitizedName = name.trim().replace(/[<>]/g, '');
return vopApi.verify(iban, sanitizedName);
}
// ❌ Wrong - Direct use without validation
function verifyPayee(iban, name) {
return vopApi.verify(iban, name); // No validation!
}
// ✅ Correct - Don't store sensitive verification data
const result = await vopApi.verify(iban, name);
if (result.result === 'MATCH') {
// Store only the verification result, not the data
await database.saveVerificationResult({
transactionId: txnId,
result: result.result,
timestamp: new Date()
});
}
// ❌ Wrong - Storing sensitive payment data
await database.saveVerificationData({
iban: iban, // Don't store IBAN
name: name, // Don't store names
result: result.result
});
// ✅ Correct - Generic error messages to users
try {
const result = await vopApi.verify(iban, name);
return result;
} catch (error) {
// Log detailed error for debugging
logger.error('VoP verification failed', { error, iban: maskIBAN(iban) });
// Return generic error to user
throw new Error('Verification service temporarily unavailable');
}
// ❌ Wrong - Exposing internal details
try {
const result = await vopApi.verify(iban, name);
return result;
} catch (error) {
// Don't expose internal errors to users
throw new Error(`Database connection failed: ${error.message}`);
}
// ✅ Correct - Mask sensitive data in logs
function maskIBAN(iban) {
return iban.replace(/(.{4})(.*)(.{4})/, '$1****$3');
}
logger.info('VoP verification request', {
iban: maskIBAN(iban),
result: result.result,
timestamp: new Date()
});
// ❌ Wrong - Logging sensitive data
logger.info('VoP verification request', {
iban: iban, // Don't log full IBAN
name: name, // Don't log names
result: result.result
});
class VoPRateLimiter {
constructor(maxRequests = 100, windowMs = 60000) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}
async verify(iban, name) {
// Check rate limit before making request
if (!this.canMakeRequest()) {
throw new Error('Rate limit exceeded. Please try again later.');
}
this.requests.push(Date.now());
return await vopApi.verify(iban, name);
}
canMakeRequest() {
const now = Date.now();
this.requests = this.requests.filter(time => now - time < this.windowMs);
return this.requests.length < this.maxRequests;
}
}
async function verifyWithRetry(iban, name, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await vopApi.verify(iban, name);
} catch (error) {
if (error.status === 429) { // Rate limited
const retryAfter = error.headers['retry-after'] || 60;
await delay(retryAfter * 1000);
continue;
}
throw error; // Re-throw non-rate-limit errors
}
}
throw new Error('Max retries exceeded');
}
// ✅ Correct - Handle all response types securely
function handleVoPResult(result, paymentAmount) {
switch (result.result) {
case 'MATCH':
// Proceed with payment
return processPayment(paymentAmount);
case 'NO_MATCH':
// Block payment - potential fraud
logSecurityEvent('FRAUD_DETECTED', { result });
throw new Error('Payment blocked - recipient verification failed');
case 'PARTIAL_MATCH':
// Require additional verification for high-value payments
if (paymentAmount > 10000) {
return requireManualApproval(result);
}
return processPayment(paymentAmount);
default:
// Handle unexpected responses
logSecurityEvent('UNEXPECTED_RESPONSE', { result });
throw new Error('Verification service error');
}
}
// ❌ Wrong - Ignoring security implications
function handleVoPResult(result) {
if (result.result === 'MATCH') {
return processPayment();
}
// Missing handling for NO_MATCH and PARTIAL_MATCH!
}
// ✅ Correct - Integrate with fraud detection
async function verifyPayment(iban, name, amount, userContext) {
// Step 1: VoP verification
const vopResult = await vopApi.verify(iban, name);
// Step 2: Additional fraud checks for NO_MATCH
if (vopResult.result === 'NO_MATCH') {
await fraudDetection.reportSuspiciousActivity({
iban: maskIBAN(iban),
amount: amount,
userContext: userContext,
reason: 'VoP_NO_MATCH'
});
// Block payment
throw new Error('Payment blocked - security check failed');
}
return vopResult;
}
// Monitor for suspicious patterns
class VoPSecurityMonitor {
constructor() {
this.failureCount = new Map();
this.alertThreshold = 10;
}
recordFailure(clientId, error) {
const count = this.failureCount.get(clientId) || 0;
this.failureCount.set(clientId, count + 1);
// Alert on suspicious activity
if (count >= this.alertThreshold) {
this.alertSecurityTeam({
clientId,
failureCount: count,
lastError: error,
timestamp: new Date()
});
}
}
alertSecurityTeam(incident) {
// Send alert to security team
securityLogger.alert('Suspicious VoP activity detected', incident);
}
}
// ✅ Correct - Maintain audit trail
async function auditedVerification(iban, name, userId, transactionId) {
const auditEntry = {
timestamp: new Date(),
userId: userId,
transactionId: transactionId,
action: 'VOP_VERIFICATION',
iban: maskIBAN(iban), // Masked for privacy
ipAddress: getClientIP(),
userAgent: getUserAgent()
};
try {
const result = await vopApi.verify(iban, name);
// Log successful verification
auditLogger.info('VoP verification completed', {
...auditEntry,
result: result.result,
confidence: result.confidence
});
return result;
} catch (error) {
// Log failed verification
auditLogger.error('VoP verification failed', {
...auditEntry,
error: error.message
});
throw error;
}
}