KYC/AML Development for Crypto Exchanger
KYC (Know Your Customer) and AML (Anti-Money Laundering) are regulatory requirements that become mandatory for crypto exchangers in most jurisdictions. FATF (Financial Action Task Force) and the EU require verification for transactions above certain thresholds. Without KYC/AML, the exchanger risks payment provider blocking and legal problems.
KYC/AML System Architecture
Verification Tiers (KYC Tiers)
type KYCTier int
const (
TierUnverified KYCTier = 0 // unverified
TierBasic KYCTier = 1 // email + phone
TierStandard KYCTier = 2 // ID document + selfie
TierEnhanced KYCTier = 3 // + source of funds
)
type TierLimits struct {
DailyLimitUSD decimal.Decimal
TxLimitUSD decimal.Decimal
RequiredKYC KYCTier
}
var DefaultLimits = map[KYCTier]TierLimits{
TierUnverified: {DailyLimitUSD: decimal.New(1000, 0), TxLimitUSD: decimal.New(500, 0)},
TierBasic: {DailyLimitUSD: decimal.New(3000, 0), TxLimitUSD: decimal.New(1500, 0)},
TierStandard: {DailyLimitUSD: decimal.New(15000, 0), TxLimitUSD: decimal.New(5000, 0)},
TierEnhanced: {DailyLimitUSD: decimal.New(100000, 0), TxLimitUSD: decimal.New(50000, 0)},
}
Integration with KYC Providers
Developing OCR for documents and liveness detection yourself is not cost-effective. Use KYC-as-a-Service:
Sumsub — popular choice for crypto. Document verification for 80+ countries, liveness detection, Watchlist database:
import axios from 'axios';
import crypto from 'crypto';
class SumsubKYC {
private appToken: string;
private secretKey: string;
private signRequest(method: string, url: string, body?: string): string {
const ts = Math.floor(Date.now() / 1000).toString();
const payload = ts + method.toUpperCase() + url + (body || '');
const hmac = crypto.createHmac('sha256', this.secretKey).update(payload).digest('hex');
return `${ts}:${hmac}`;
}
async createApplicant(externalUserId: string, levelName: string = 'basic-kyc-level') {
const url = '/resources/applicants';
const body = JSON.stringify({ externalUserId, levelName });
const response = await axios.post(`https://api.sumsub.com${url}`, body, {
headers: {
'X-App-Token': this.appToken,
'X-App-Access-Sig': this.signRequest('POST', url, body),
'Content-Type': 'application/json',
},
});
return response.data; // applicantId
}
async generateAccessToken(applicantId: string): Promise<string> {
const url = `/resources/accessTokens?userId=${applicantId}`;
const response = await axios.post(`https://api.sumsub.com${url}`, '', {
headers: {
'X-App-Token': this.appToken,
'X-App-Access-Sig': this.signRequest('POST', url),
},
});
return response.data.token;
}
// Webhook from Sumsub on verification status change
async handleWebhook(payload: SumsubWebhook) {
const { applicantId, reviewResult } = payload;
switch (reviewResult.reviewAnswer) {
case 'GREEN': // verified
await this.updateUserKYCStatus(applicantId, 'verified');
break;
case 'RED': // rejected
const reasons = reviewResult.rejectLabels;
await this.updateUserKYCStatus(applicantId, 'rejected', reasons);
break;
}
}
}
Frontend integration — Sumsub WebSDK embedded in verification page:
// React verification component
import SumsubWebSdk from '@sumsub/websdk-react';
function KYCVerification({ userId }: { userId: string }) {
const [accessToken, setAccessToken] = useState<string | null>(null);
useEffect(() => {
api.getKYCToken(userId).then(setAccessToken);
}, [userId]);
if (!accessToken) return <Loading />;
return (
<SumsubWebSdk
accessToken={accessToken}
expirationHandler={() => api.getKYCToken(userId)}
onMessage={(type, payload) => {
if (type === 'idCheck.applicantReviewComplete') {
router.push('/kyc/pending');
}
}}
onError={(error) => console.error('KYC error:', error)}
/>
);
}
AML: Transaction Monitoring
Blockchain Analytics
For crypto transactions, you need to check source of funds. Sending from mixer, darknet marketplace, or sanctioned address — red flag.
Blockchain analytics providers:
class ChainAnalysisAML {
async checkAddress(address: string, currency: string): Promise<RiskScore> {
const response = await this.client.post('/v2/users', {
userId: `check-${Date.now()}`,
currency,
address,
});
return {
risk: response.data.risk, // "low" | "medium" | "high" | "severe"
riskScore: response.data.riskScore, // 0-10
exposures: response.data.exposures, // [{category, value}]
flags: response.data.flags, // specific risk reasons
};
}
}
// Example integration into exchange process
async function checkIncomingDeposit(txHash: string, fromAddress: string, currency: string) {
const riskScore = await chainAnalysis.checkAddress(fromAddress, currency);
if (riskScore.risk === 'severe') {
await freezeTransaction(txHash, 'HIGH_RISK_ADDRESS');
await notifyCompliance(txHash, riskScore);
return false;
}
if (riskScore.risk === 'high') {
await flagForReview(txHash, riskScore);
// Continue, but mark for manual review
}
return true;
}
Elliptic and TRM Labs — Chainalysis alternatives, with different coverage and pricing.
AML Rules
type AMLRule struct {
Name string
Check func(tx Transaction, user User) AMLResult
Action AMLAction // ALLOW, FLAG, BLOCK
}
var AMLRules = []AMLRule{
{
Name: "structuring_detection",
Check: func(tx Transaction, user User) AMLResult {
// Suspicious: several transactions just below KYC threshold
recentTxs := db.GetUserTransactions(user.ID, 24*time.Hour)
totalValue := sum(recentTxs)
// Several ~$990 transactions in a row — attempt to bypass KYC limit
if len(recentTxs) > 3 && totalValue > KYCThreshold*0.8 {
return AMLResult{Risk: "high", Reason: "potential_structuring"}
}
return AMLResult{Risk: "low"}
},
Action: FLAG,
},
{
Name: "high_risk_country",
Check: func(tx Transaction, user User) AMLResult {
highRiskCountries := []string{"KP", "IR", "SY", "CU"}
if contains(highRiskCountries, user.Country) {
return AMLResult{Risk: "severe", Reason: "sanctioned_jurisdiction"}
}
return AMLResult{Risk: "low"}
},
Action: BLOCK,
},
{
Name: "pep_screening",
Check: func(tx Transaction, user User) AMLResult {
// PEP = Politically Exposed Person
isPEP := pepDatabase.CheckName(user.FullName, user.DateOfBirth)
if isPEP {
return AMLResult{Risk: "medium", Reason: "pep_detected"}
}
return AMLResult{Risk: "low"}
},
Action: FLAG,
},
}
Watchlist Screening
Checking against sanctions lists (OFAC SDN, EU Consolidated List, UN Sanctions):
type WatchlistScreener struct {
ofacList []SanctionEntry
euList []SanctionEntry
updateFreq time.Duration
}
func (ws *WatchlistScreener) ScreenName(name, dob string) []SanctionMatch {
// Fuzzy matching — names can be transliterated differently
var matches []SanctionMatch
for _, entry := range append(ws.ofacList, ws.euList...) {
similarity := fuzzy.MatchScore(name, entry.Name)
if similarity > 0.85 { // 85% similarity
matches = append(matches, SanctionMatch{
Entry: entry,
Similarity: similarity,
ListSource: entry.ListSource,
})
}
}
return matches
}
SAR (Suspicious Activity Report)
When suspicious activity is detected — mandatory SAR filing to regulator (FinCEN in US, Rosfinmonitoring in Russia):
type SAR struct {
UserID int64
TransactionID string
Amount decimal.Decimal
Currency string
SuspiciousType string // structuring, layering, unusual_pattern, etc.
Description string
SupportingDocs []string
FiledAt time.Time
}
// Auto-generate SAR draft on alert
func (c *ComplianceEngine) GenerateSARDraft(alert AMLAlert) *SAR {
user := c.db.GetUser(alert.UserID)
txHistory := c.db.GetTransactionHistory(alert.UserID, 90*24*time.Hour)
return &SAR{
UserID: alert.UserID,
TransactionID: alert.TxID,
SuspiciousType: alert.RuleTriggered,
Description: fmt.Sprintf(
"User %s (KYC: %s) made transaction %s %s "+
"from high-risk address (score: %.1f). "+
"90-day transaction history: %d operations for %s",
user.FullName, user.KYCTier, alert.Amount, alert.Currency,
alert.RiskScore, len(txHistory), totalVolume(txHistory),
),
}
}
Compliance Dashboard
For compliance team — separate interface:
- Queue for manual review (high/medium risk transactions)
- History of all AML alerts with details
- Statistics: transactions blocked, SARs filed
- AML rule and threshold management
Timeline and Integrations
| Component | Timeline |
|---|---|
| KYC tiers + Sumsub integration | 2–3 weeks |
| AML rules + transaction monitoring | 2–3 weeks |
| Blockchain analytics (Chainalysis/Elliptic) | 1–2 weeks |
| Watchlist screening | 1–2 weeks |
| Compliance dashboard | 2–3 weeks |
Complete KYC/AML system for exchanger: 2–3 months. Requires legal consultation for target jurisdictions to configure thresholds and requirements.







