Chainalysis Integration for Transaction Monitoring
Chainalysis KYT (Know Your Transaction) is the industry standard for crypto transaction monitoring. Key advantage: Chainalysis builds a graph of the entire blockchain history, not just checking the direct transaction — it tracks funds through multiple hops (indirect exposure).
KYT API Architecture
Chainalysis KYT v2 API works on the model: register users and transfers, get risk data. Two modes: synchronous (immediate response) and asynchronous (webhook callback for complex cases).
class ChainalysisKYTService {
private readonly API_BASE = "https://api.chainalysis.com/api/kyt/v2";
// Register new user wallet
async registerUser(userId: string, asset: string): Promise<void> {
await this.apiCall("POST", "/users", { userId, asset });
}
// Screen incoming transfer (deposit)
async registerReceivedTransfer(params: {
network: string;
asset: string;
transferReference: string; // txHash for on-chain, or external ID
userId: string;
outputAddress: string;
assetAmount: number;
timestamp: string; // ISO 8601
}): Promise<ReceivedTransferResponse> {
return this.apiCall("POST", "/transfers/received", {
network: params.network,
asset: params.asset,
transferReference: params.transferReference,
userId: params.userId,
outputAddress: params.outputAddress,
assetAmount: params.assetAmount,
timestamp: params.timestamp,
});
}
// Screen outgoing transfer (withdrawal)
async registerSentTransfer(params: {
network: string;
asset: string;
transferReference: string;
userId: string;
outputAddress: string;
assetAmount: number;
}): Promise<SentTransferResponse> {
return this.apiCall("POST", "/transfers/sent", params);
}
// Get alerts for transfer
async getTransferAlerts(externalId: string): Promise<Alert[]> {
const response = await this.apiCall("GET", `/transfers/${externalId}/alerts`);
return response.alerts;
}
private async apiCall(method: string, path: string, body?: any): Promise<any> {
const response = await fetch(`${this.API_BASE}${path}`, {
method,
headers: {
"Token": this.apiKey,
"Content-Type": "application/json",
"Accept": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
const error = await response.json();
throw new ChainalysisError(response.status, error.message);
}
return response.json();
}
}
Processing Risk Response
interface KYTRiskResponse {
externalId: string;
asset: string;
network: string;
transferReference: string;
updatedAt: string;
status: "APPROVED" | "BLOCKED" | "IN_REVIEW";
cluster: {
name: string;
category: string; // "darknet_market", "exchange", "sanctions", etc.
} | null;
riskScore: number; // 0-100
}
async function handleDepositScreening(deposit: Deposit): Promise<void> {
const response = await chainalysis.registerReceivedTransfer({
network: deposit.blockchain,
asset: deposit.token,
transferReference: deposit.txHash,
userId: deposit.userId,
outputAddress: deposit.toAddress,
assetAmount: deposit.amount,
timestamp: deposit.timestamp.toISOString(),
});
// Wait for processing (usually 1-30 seconds)
const riskData = await pollForResult(response.externalId);
if (riskData.status === "BLOCKED" || riskData.riskScore >= 70) {
await db.freezeDeposit(deposit.id, riskData.riskScore, riskData.cluster?.category);
await alertComplianceTeam({
depositId: deposit.id,
userId: deposit.userId,
riskScore: riskData.riskScore,
category: riskData.cluster?.category,
externalId: response.externalId,
});
return;
}
if (riskData.status === "IN_REVIEW" || riskData.riskScore >= 40) {
await db.holdForManualReview(deposit.id, riskData.riskScore);
await createComplianceTask(deposit, riskData);
return;
}
await db.approveDeposit(deposit.id);
await creditUserBalance(deposit);
}
Webhook for Async Results
Chainalysis sends a webhook when analysis completes:
app.post("/webhooks/chainalysis", async (req, res) => {
const { externalId, asset, updatedAt, status, riskScore, cluster, alerts } = req.body;
// Find pending transaction
const deposit = await db.findDepositByExternalId(externalId);
if (!deposit) return res.status(404).send();
if (status === "BLOCKED" || riskScore >= 70) {
await db.freezeDeposit(deposit.id, riskScore, cluster?.category);
await alertCompliance(deposit, { riskScore, cluster, alerts });
} else if (status === "IN_REVIEW") {
await createManualReviewTask(deposit, { riskScore, alerts });
} else {
await approveDeposit(deposit.id);
}
res.status(200).send();
});
Alert Categories and Processing Rules
| Category | Action |
|---|---|
| sanctions | Block + immediate compliance alert |
| darknet_market | Block + SAR draft |
| ransomware | Block + SAR draft |
| stolen_funds | Block |
| mixing / tumbling | Review + Enhanced Monitoring |
| p2p_exchange | Review (depends on volume) |
| gambling | Review (depends on license) |
| exchange | Usually pass |
Chainalysis KYT integration for deposit and withdrawal monitoring with webhook processing and compliance alerting — 1-2 weeks development.







