ic within a ComplianceService class. This isolates API interactions and error handling from business logic.
2. Fail-Closed Strategy: If the screening service is unreachable or returns an error, the operation must be blocked. Defaulting to "allow" on failure is a critical compliance violation.
3. Concurrency Control: Batch screening jobs must respect rate limits and avoid overwhelming downstream services. A concurrency-limited processor is essential for retroactive screening of existing user bases.
4. Immutable Audit Trail: Every screening request and result must be logged with a timestamp, entity details, and outcome for regulatory audits.
Implementation: Compliance Service
import axios, { AxiosInstance } from 'axios';
export interface ScreeningConfig {
baseUrl: string;
apiKey: string;
timeoutMs?: number;
}
export interface ScreeningRequest {
entityName: string;
entityType: 'individual' | 'organization';
matchThreshold: number; // 0-100
}
export interface ScreeningMatch {
matchedName: string;
confidence: number;
program: string;
aliases?: string[];
}
export interface ScreeningResult {
isFlagged: boolean;
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
confidence: number;
matches: ScreeningMatch[];
screenedAt: string;
}
export class ComplianceService {
private client: AxiosInstance;
private config: ScreeningConfig;
constructor(config: ScreeningConfig) {
this.config = { timeoutMs: 5000, ...config };
this.client = axios.create({
baseURL: this.config.baseUrl,
timeout: this.config.timeoutMs,
headers: {
Authorization: `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
},
});
}
async screenEntity(request: ScreeningRequest): Promise<ScreeningResult> {
try {
const response = await this.client.post('/v1/screen', {
name: request.entityName,
type: request.entityType,
threshold: request.matchThreshold,
});
return this.parseResponse(response.data);
} catch (error) {
// Fail-closed: Block operation if screening cannot be verified
console.error('Compliance screening failed:', error);
throw new Error('Screening service unavailable. Operation blocked for compliance.');
}
}
private parseResponse(data: any): ScreeningResult {
return {
isFlagged: data.data.isMatch,
riskLevel: data.data.riskLevel,
confidence: data.data.confidence,
matches: data.data.matches.map((m: any) => ({
matchedName: m.matchedName,
confidence: m.confidence,
program: m.program,
aliases: m.aliases,
})),
screenedAt: data.data.screenedAt,
};
}
}
Integration: Batch Screening Processor
For retroactive screening of existing databases, a batch processor with concurrency control prevents rate-limit violations and ensures predictable resource usage.
import pLimit from 'p-limit';
export class BatchScreeningProcessor {
constructor(private complianceService: ComplianceService) {}
async processBatch(
requests: ScreeningRequest[],
concurrency: number = 5
): Promise<ScreeningResult[]> {
const limit = pLimit(concurrency);
const tasks = requests.map((req) =>
limit(() => this.complianceService.screenEntity(req))
);
const results = await Promise.allSettled(tasks);
return results
.filter(
(result): result is PromiseFulfilledResult<ScreeningResult> =>
result.status === 'fulfilled'
)
.map((result) => result.value);
}
}
Rationale: Using p-limit (or a similar concurrency primitive) ensures that batch jobs do not spike API usage. Promise.allSettled allows the processor to continue even if individual requests fail, though in a strict compliance context, you may choose to fail the entire batch if errors exceed a threshold.
Pitfall Guide
| Pitfall | Explanation | Fix |
|---|
| Fail-Open on Error | Allowing transactions to proceed when the screening service times out or returns an error. | Implement strict error handling that throws exceptions and blocks the workflow. Never default to allow. |
| Threshold Misconfiguration | Setting the match threshold too high (e.g., 95%) causes false negatives for transliterated names. Setting it too low (e.g., 60%) floods manual review queues. | Use 80-85% for standard KYC. Adjust to 75% for regions with high transliteration variance (Arabic, Cyrillic). Tune based on false positive rates. |
| Missing Audit Logs | Failing to record screening results prevents demonstrating compliance during regulatory audits. | Log every screening event with entity details, risk level, confidence score, and timestamp. Store logs in an immutable append-only store. |
| Ignoring PEP Screening | Screening only against OFAC SDN while missing Politically Exposed Persons (PEPs), which is required by many AML frameworks. | Integrate PEP screening alongside SDN. Aggregate risk levels from both lists to determine the final risk posture. |
| Rate Limit Violations | Sending batch requests without concurrency control triggers API rate limits, causing service degradation. | Use a concurrency limiter in batch processors. Implement exponential backoff for retry logic. |
| Name Normalization Gaps | Failing to handle suffixes (Jr., Sr.), titles, or diacritics can affect matching accuracy if preprocessing is done client-side. | Rely on the screening provider's normalization capabilities. If preprocessing, strip diacritics and normalize whitespace consistently. |
| Stale List Data | Using a cached version of the SDN list that hasn't been updated. The list changes weekly. | If self-hosting, automate daily list ingestion. If using an API, verify the provider updates their database frequently. |
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Real-time Onboarding | Managed API Service | Low latency, high accuracy, no infrastructure overhead. | Per-request pricing. |
| Strict Data Residency | Self-Hosted Engine | Keeps all data within private infrastructure. | Higher infra and maintenance costs. |
| Retroactive Screening | Async Batch Worker | Processes large volumes without blocking user traffic. | Compute costs for worker nodes. |
| High-Volume Payments | Managed API with Caching | Reduces API calls for repeat entities while maintaining compliance. | Reduced API costs via cache hits. |
Configuration Template
Use environment variables to manage sensitive configuration and thresholds. This template supports multi-region deployments and threshold tuning.
# Compliance Service Configuration
COMPLIANCE_API_URL=https://api.compliance-provider.example.com/v1
COMPLIANCE_API_KEY=sk_live_XXXXXXXXXXXXXXXXXXXX
COMPLIANCE_TIMEOUT_MS=5000
# Screening Thresholds
THRESHOLD_INDIVIDUAL=80
THRESHOLD_ORGANIZATION=85
THRESHOLD_PEP=75
# Batch Processing
BATCH_CONCURRENCY=5
BATCH_RETRY_ATTEMPTS=3
Quick Start Guide
- Install Dependencies:
npm install axios p-limit
- Initialize the Service:
const service = new ComplianceService({
baseUrl: process.env.COMPLIANCE_API_URL!,
apiKey: process.env.COMPLIANCE_API_KEY!,
});
- Screen an Entity:
const result = await service.screenEntity({
entityName: 'Vladimir Poutine',
entityType: 'individual',
matchThreshold: parseInt(process.env.THRESHOLD_INDIVIDUAL || '80'),
});
- Handle Result:
if (result.riskLevel === 'CRITICAL' || result.riskLevel === 'HIGH') {
// Block transaction and trigger compliance alert
throw new Error('Entity flagged by sanctions screening.');
}
// Proceed with onboarding
- Run Batch Job:
const processor = new BatchScreeningProcessor(service);
const results = await processor.processBatch(userRequests, 5);
// Process flagged results