Atlorium

Приняли криптоплатёж — приняли и его историю. Что показывает AML-скоринг кошелька

У криптоперевода нет банка, который проверит отправителя за вас. Проверка адреса до зачисления — единственный момент, когда решение ещё стоит один запрос, а не блокировку счёта.

10 минут чтения комплаенс · юрист · криптобизнес · финансовый директор

Платёж пришёл в пятницу вечером: 42 000 USDT, адрес отправителя незнакомый, сумма совпадает со счётом до цента. Заказ закрыли, в понедельник склад отгрузил товар.

Через полтора месяца биржа, куда компания вывела эту выручку, заморозила аккаунт и попросила объяснить происхождение средств. Выяснилось, что монеты, которыми расплатился клиент, за два перевода до вас прошли через миксер. Клиент, скорее всего, и сам этого не знал: купил USDT у знакомого «по хорошему курсу». Но объясняться с биржей, писать пояснительную и ждать разморозки — не ему.

Обычный банковский платёж в такой ситуации выглядит иначе. Между вами и историей денег стоит банк отправителя: он проводит собственную проверку, он несёт часть ответственности, и если платёж дошёл — вопрос о происхождении к вам, как правило, уже не задают. В крипте этого посредника нет. Деньги приходят вместе со всей своей биографией, и с момента зачисления она становится вашей.

Точка, в которой это ещё можно остановить, ровно одна: до того, как вы зачислили платёж и отгрузили товар. Дальше блокчейн не откатывается.

Куда девается банк-посредник

Публичный адрес отправителя виден вам сразу — он лежит в самой транзакции. Что за ним стоит, не видно никак: адрес не подписан именем, не привязан к юрлицу и ничего не рассказывает о том, откуда на нём взялись монеты. Чтобы это узнать, нужно пройти по цепочке переводов назад и понять, что за кошельки в ней участвовали. Ручками это делают в обозревателе блокчейна, и на втором-третьем шаге человек обычно сдаётся: адресов становится десятки, а имён у них по-прежнему нет.

Атрибуция — это и есть то, за что платят в AML-скоринге. Не «показать транзакции» (их и так видно бесплатно), а сопоставить адреса с известными сущностями: вот это депозитный адрес лицензированной биржи, это — миксер, это — кошелёк из санкционного перечня, это — сборный адрес мошеннической схемы.

Деньги пришли — и история пришла вместе с ними

Что происходит
Адрес отправителя незнакомый. Откуда он получил монеты и через кого они шли до этого, никто не смотрел: транзакция подтверждена, сумма верная.
Во что обходится
Вопрос прилетает через недели, когда выручку выводят на биржу или в фиат: заморозка, объяснительная, в худшем случае удержание суммы до выяснения.
Чем закрывается
Скрининг адреса до зачисления возвращает разбивку источников средств по категориям (sourceOfFunds) и флаг санкционной экспозиции — до того, как товар уехал со склада.

Контрагент прошёл KYC, а платёж — нет

Что происходит
Клиент прислал паспорт, подписал документы и выглядит безупречно. Но платит он не со своего кошелька, а с адреса, у которого прямая связь с санкционным.
Во что обходится
Проверенным считается человек, а санкционным становится платёж. Для регулятора и для банка-эквайера это разные проверки, и вторую вы не делали.
Чем закрывается
Флаг sanctionsHit в ответе относится к конкретному адресу, а не к анкете клиента. Это отдельный вопрос, и задавать его нужно отдельно.

Проверка вручную стоит час и всё равно неполная

Что происходит
Комплаенс-офицер открывает обозреватель, кликает по входящим транзакциям, выписывает адреса в блокнот и пытается понять, чьи они.
Во что обходится
От 30 до 60 минут на один адрес — и в конце всё равно неизвестно, что кошелёк двумя переводами раньше был миксером: в обозревателе он выглядит как обычный адрес.
Чем закрывается
Один POST-запрос отдаёт балл, флаги, категории контрагентов и глубину связи (hops, isDirect) — результат воспроизводимый и его можно положить в лог как доказательство проверки.

Сколько стоит зачислить не тот платёж

Считать здесь надо не «экономию времени», а асимметрию. Проверка стоит один запрос к API. Не-проверка стоит ровно ноль — до того момента, когда она стоит всей суммы платежа плюс отгруженного товара плюс нескольких недель переписки с биржей.

Смотрим в обозревателе руками Скрининг по API
Время на один адрес 30–60 минут, и глубже двух-трёх переходов человек не идёт Секунды; фактическое время приходит в поле elapsedMs
Что видно Транзакции и суммы. Кому принадлежат адреса — нет Категории контрагентов: биржа, миксер, даркнет, мошенничество, гэмблинг, P2P
Санкции Сверять адрес со списками вручную Отдельный флаг sanctionsHit — прямая и косвенная экспозиция
Воспроизводимость Зависит от того, кто и насколько внимательно смотрел Одинаковый JSON, который можно сохранить и предъявить
Цена Час работы комплаенс-офицера Один платный запрос, цена — на странице тарифов

Отдельная строка расходов, о которой вспоминают поздно: у платёжного партнёра или банка, через который вы выводите крипту в фиат, есть собственный комплаенс. Один зафиксированный им инцидент — это не только эти конкретные деньги, но и разговор о том, останетесь ли вы клиентом.

Как читать балл риска

Сервис AML-скоринг принимает две вещи: публичный адрес кошелька и код сети. Приватный ключ и сид-фраза не нужны и не должны нигде фигурировать — адрес и так виден всем в блокчейне, а ключ не спрашивает ни один честный сервис.

Поддерживаются двенадцать сетей: BTC, ETH, BNB (BNB Smart Chain), MATIC (Polygon), TRX (Tron), SOL (Solana), LTC, XRP, BCH, DOGE, а также USDT и USDC. Сеть указывается явно: один и тот же формат адреса живёт в разных EVM-сетях, и угадывать за вас сервис не будет.

В ответе несколько слоёв, и читать их надо в правильном порядке.

riskScore — нормализованная оценка от 0 (риск не обнаружен) до 100 (максимальный). Это сводка по всей экспозиции: и по тому, откуда деньги приходили, и по тому, куда уходили, и по контрагентам. Балл удобен для сортировки очереди на ручной разбор, но строить бизнес-правило прямо на числе — плохая идея.

severity — то, на чём правило стоит строить. Пять значений: UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL. Это человеко-понятная категория поверх балла. Мы намеренно не публикуем таблицу «столько-то баллов = HIGH»: соответствие балла уровню задаёт источник данных, и привязываться к конкретным числам — значит однажды получить сюрприз при обновлении их модели. Уровень стабильнее числа.

sanctionsHit — отдельный булев флаг, и это самый сильный сигнал во всём ответе. Он означает, что у адреса нашлась прямая или косвенная связь с санкционными адресами и организациями. Не «высокий риск», а именно санкционная экспозиция, и обходиться с ним нужно не как с числом, а как со стоп-краном.

pepCounterparty — связь с публичным должностным лицом. Это не запрет: PEP имеет право платить за товар. Это повод включить усиленную проверку и посмотреть на сделку внимательнее, чем обычно.

sourceOfFunds и destinationOfFunds — самое интересное для человека, который потом будет писать пояснение. Это списки: откуда деньги приходили и куда уходили, с разбивкой по категориям контрагентов (category: например, exchange_licensed, mixer, darknet, scam, sanctioned, gambling, p2p), долей (percentage) и суммой в долларах (amountUsd). Здесь же hops — число «прыжков» до контрагента, isDirect и exposureType со значениями direct или indirect.

Разница между прямой и косвенной связью — это ровно та разница, из-за которой одинаковый балл значит разное. Кошелёк, который получил деньги напрямую с миксера, и кошелёк, у которого миксер нашёлся в четырёх переводах позади, — это разные разговоры с клиентом. Первый случай почти всегда осознанный. Второй нередко означает, что человек купил монеты на сомнительной площадке и понятия не имеет, что с ними было до него.

counterpartyConnections — список контрагентов с уровнем риска (riskLevel: low, medium, high, severe, unknown), категориями, оборотами в обе стороны (receivedUsd, sentUsd) и страной. Плюс summary — готовая строка вроде «Риск не обнаружен (0/100)», которую можно показать оператору как есть, и dominantRiskCategory — категория с наибольшим вкладом в балл (или null, если выраженной высокорисковой категории нет).

ALLOW, REVIEW, BLOCK и почему порог — ваш

Соблазн большой: взять из статьи в интернете «блокируем всё выше 70» и вписать в код. Так делать не надо, и вот почему.

Порог — это функция от вашего среднего чека, обратимости сделки и аппетита к риску. Магазин цифровых товаров со средним чеком в три тысячи рублей может спокойно пропускать MEDIUM: цена ошибки — стоимость одной лицензии, а каждый ручной разбор дороже товара. Обменник, где средний чек — два миллиона и деньги уходят необратимо, отправляет на ручную проверку уже MEDIUM, а на HIGH останавливает сделку целиком. Оба правы. Единственного правильного числа тут не существует, есть ваша риск-политика.

Что стоит зафиксировать письменно, до того как писать код:

  1. Какие исходы ведут к BLOCK без вариантов (санкционная экспозиция почти у всех попадает сюда).
  2. Какие уходят в REVIEW — очередь ручного разбора, и кто в ней работает.
  3. Что делать с UNKNOWN и с недоступностью источника: это «нет данных», а не «чисто».
  4. Сколько времени вы держите платёж в ожидании решения и что говорите клиенту в это время.

Четвёртый пункт забывают чаще всего, а он и ломает процесс: адрес заскорился как рискованный, а что делать с деньгами, которые уже в блокчейне и назад не отзываются, никто не решил. Отправить обратно? На тот же адрес, который вы считаете рискованным? Заморозить до выяснения и запросить у клиента документы? Это должно быть написано до первого срабатывания, а не придумываться в пятницу вечером.

Код: проверка адреса до зачисления

Запрос ровно один: POST /api/aml/screen с телом из двух полей. Скопируйте и запустите — демо-ключ публичный, регистрация не нужна.

Скрининг адреса перед зачислением платежа

curl -X POST "https://atlorium.com/api/aml/screen" \
     -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     -H "Content-Type: application/json" \
     -d '{"walletAddress":"0x26a5919cd0392df875a53873b0323c42094bb216","blockchain":"BNB"}'
        

import requests

resp = requests.post(
    "https://atlorium.com/api/aml/screen",
    json={
        "walletAddress": "0x26a5919cd0392df875a53873b0323c42094bb216",
        "blockchain": "BNB",
    },
    headers={"Authorization": "Bearer ak_sandbox_demo_mockdata_v1"},
    timeout=20,
)

# 503 — источник недоступен. Деньги за такой запрос не списываются:
# платёж не зачисляем и не отклоняем, а откладываем и повторяем позже.
if resp.status_code == 503:
    raise RuntimeError("Скоринг недоступен — платёж остаётся в ожидании")

data = resp.json()

# Пороги ниже — ПРИМЕР. Свои возьмите из риск-политики, а не отсюда.
if data["sanctionsHit"]:
    decision = "BLOCK"
elif data["severity"] in ("HIGH", "CRITICAL"):
    decision = "BLOCK"
elif data["severity"] in ("MEDIUM", "UNKNOWN") or data["pepCounterparty"]:
    decision = "REVIEW"
else:
    decision = "ALLOW"

print(decision, data["riskScore"], data["summary"])
        

using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer ak_sandbox_demo_mockdata_v1");

var body = new StringContent(
    """{"walletAddress":"0x26a5919cd0392df875a53873b0323c42094bb216","blockchain":"BNB"}""",
    Encoding.UTF8, "application/json");

var response = await http.PostAsync("https://atlorium.com/api/aml/screen", body);

// Сбой источника: списания не было, решение по платежу не принимаем.
if ((int)response.StatusCode == 503)
    throw new InvalidOperationException("Скоринг недоступен — повторим позже");

using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var root = doc.RootElement;

var severity = root.GetProperty("severity").GetString();
var sanctions = root.GetProperty("sanctionsHit").GetBoolean();
var pep = root.GetProperty("pepCounterparty").GetBoolean();

// Пороги — пример. Настоящие живут в вашей риск-политике.
var decision = sanctions || severity is "HIGH" or "CRITICAL"
    ? "BLOCK"
    : severity is "MEDIUM" or "UNKNOWN" || pep
        ? "REVIEW"
        : "ALLOW";
        

{
  "walletAddress": "0x26a5919cd0392df875a53873b0323c42094bb216",
  "blockchain": "BNB",
  "screeningType": "WALLET_SCREENING",
  "riskScore": 0,
  "severity": "LOW",
  "status": "SCREENED",
  "summary": "Риск не обнаружен (0/100)",
  "dominantRiskCategory": null,
  "sanctionsHit": false,
  "pepCounterparty": false,
  "totalValueUsd": 184305.71,
  "feeUsd": 42.18,
  "sourceOfFunds": [],
  "destinationOfFunds": [
    {
      "category": "exchange_licensed",
      "percentage": 100,
      "amountUsd": 184305.71,
      "entityName": "Kessler-Waters Exchange",
      "entityType": "Exchange",
      "entitySubtype": "Licensed / KYC",
      "hops": 1,
      "description": "Exchange / Licensed",
      "isDirect": true,
      "exposureDirection": "outgoing",
      "exposureType": "direct",
      "country": "MT"
    }
  ],
  "counterpartyConnections": [
    {
      "entityName": "Kessler-Waters Exchange",
      "entityType": "Exchange",
      "riskLevel": "low",
      "categories": ["exchange"],
      "percentage": 100,
      "isDirect": true
    }
  ],
  "elapsedMs": 3
}
        

Тот же вызов на шести языках (Python, TypeScript, Go, Java, C#, PHP) — в репозитории aml-crypto-screening-api-client. Примеры запускаются сразу: ключ в них уже стоит демонстрационный.

Это ответ песочницы: демо-ключ отдаёт моки — данные правдоподобные, но сгенерированные, и на один и тот же запрос всегда приходит один и тот же ответ. Формат настоящий, содержимое — нет, и писать на нём тесты удобно именно поэтому. Настоящий скоринг приходит с личным ключом.

Ответ с найденным риском выглядит так же по структуре, но заполнены другие поля. Значения ниже придуманы для иллюстрации — набор полей настоящий.

Как выглядит ответ, на котором платёж останавливают

curl -X POST "https://atlorium.com/api/aml/screen" \
     -H "Authorization: Bearer ВАШ_КЛЮЧ" \
     -H "Content-Type: application/json" \
     -d '{"walletAddress":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","blockchain":"BTC"}'
        

{
  "walletAddress": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
  "blockchain": "BTC",
  "riskScore": 78,
  "severity": "HIGH",
  "status": "SCREENED",
  "summary": "Высокий риск (78/100)",
  "dominantRiskCategory": "mixer",
  "sanctionsHit": false,
  "pepCounterparty": false,
  "totalValueUsd": 61240.00,
  "sourceOfFunds": [
    {
      "category": "mixer",
      "percentage": 46.2,
      "amountUsd": 28293.0,
      "entityType": "Mixer",
      "hops": 2,
      "isDirect": false,
      "exposureDirection": "incoming",
      "exposureType": "indirect",
      "country": null
    },
    {
      "category": "p2p",
      "percentage": 53.8,
      "amountUsd": 32947.0,
      "entityType": "P2P",
      "hops": 1,
      "isDirect": true,
      "exposureDirection": "incoming",
      "exposureType": "direct"
    }
  ],
  "counterpartyConnections": [
    {
      "entityName": "unknown",
      "entityType": "Mixer",
      "riskLevel": "high",
      "categories": ["mixer"],
      "receivedUsd": 28293.0,
      "percentage": 46.2,
      "isDirect": false
    }
  ]
}
        

Тот же вызов на шести языках (Python, TypeScript, Go, Java, C#, PHP) — в репозитории aml-crypto-screening-api-client. Примеры запускаются сразу: ключ в них уже стоит демонстрационный.

Что здесь видит комплаенс-офицер: санкций нет, но почти половина входящих средств пришла с миксера через два перевода (exposureType: indirect, hops: 2). Автоматически это BLOCK далеко не у всех — но в REVIEW уходит у всех.

Сводная таблица исходов, из которой удобно собрать своё правило:

Что в ответе Что это значит Типичное решение
sanctionsHit: true Прямая или косвенная связь с санкционными адресами BLOCK + эскалация. Автоматически пропускать нельзя
severity: CRITICAL / HIGH Высокая экспозиция на рисковые категории BLOCK или REVIEW — по вашей политике, но не ALLOW
severity: MEDIUM Риск есть, но не доминирует REVIEW; смотреть dominantRiskCategory и exposureType
pepCounterparty: true Среди контрагентов есть публичное должностное лицо Усиленная проверка. Само по себе не запрет
severity: UNKNOWN или statusSCREENED Данных не хватило Не ALLOW. Это «неизвестно», а не «чисто»
severity: LOW, dominantRiskCategory: null Связей с рисковыми категориями не найдено ALLOW — и всё равно сохраните ответ в журнал
HTTP 503 Источник недоступен, скрининга не было Платёж в ожидание, повтор позже. Деньги не списаны

Второй эндпоинт, GET /api/aml/validate?walletAddress=…&blockchain=…, проверяет только формат: поддерживается ли сеть и правдоподобен ли адрес (для EVM-сетей — «0x» и 40 шестнадцатеричных символов, для остальных — длина от 16 до 128 знаков без пробелов). Существование адреса в блокчейне он не проверяет и в скоринг не ходит. Учтите: этот вызов тоже проходит через общий учёт запросов, так что если вам нужна просто проверка формы поля в интерфейсе — дешевле сделать её у себя, регуляркой.

Чего скоринг не делает

Раздел, ради которого стоит читать всю статью. Если продавец AML-инструмента такого раздела не пишет — спросите его сами.

Не выносит юридическую квалификацию. Ответ не устанавливает, что средства получены преступным путём, и не является заключением. Это оценка связей адреса с категориями контрагентов по данным атрибуции. Вменять что-либо человеку на основании поля severity нельзя ни вам, ни нам.

Не гарантирует полноту санкционных списков. Атрибуция адресов — работа внешнего источника, а санкционное лицо может завести новый кошелёк сегодня утром, и размечен он будет не сегодня. sanctionsHit: false означает «связь не найдена», а не «связи нет». Отсутствие флага — не индульгенция и не защита от претензии регулятора.

Проверяет адрес, а не человека. Кто держит приватный ключ, из блокчейна не видно. Если клиент платит с депозитного адреса биржи, вы увидите биржу, а не его. AML-скоринг не заменяет KYC — он отвечает на другой вопрос и стоит рядом с ним, а не вместо.

Не отменяет транзакцию. Никакой сервис не умеет отозвать перевод в блокчейне. Всё, что даёт проверка, — возможность не зачислить платёж в свой учёт и не отгрузить товар. Ровно поэтому проверка до зачисления и стоит один запрос, а после — уже совсем других денег.

Это снимок, а не пропуск навсегда. Кошелёк живёт дальше: сегодня LOW, завтра он получил монеты с миксера и стал HIGH. Скринить надо на момент зачисления каждого платежа, а сохранённый ответ ценен как датированная запись в журнале, а не как вечное разрешение для этого адреса.

Двенадцать сетей, и только они. Сеть вне списка — ответ 400, а не пустой результат. Это сделано намеренно: молчаливый «нулевой риск» по непроверенной сети опаснее честной ошибки.

Частые вопросы

Частые вопросы

Что показывает AML-проверка криптокошелька?

По публичному адресу и сети сервис возвращает балл риска от 0 до 100, уровень серьёзности (UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL), флаги санкционной экспозиции и связи с публичным должностным лицом, разбивку источников и назначения средств по категориям контрагентов (биржи, миксеры, даркнет, мошенничество, гэмблинг, P2P) и список контрагентов с их уровнем риска и оборотами.

Нужен ли приватный ключ или сид-фраза для проверки?

Нет, и вводить их нельзя нигде и никогда. Для скрининга нужен только публичный адрес кошелька — тот, который отправитель и так сообщает вам вместе с платежом и который виден всем в блокчейне.

Какой балл риска считать опасным?

Универсального порога не существует, и любая статья, которая называет вам число, вводит в заблуждение. Порог зависит от среднего чека, обратимости сделки и вашей риск-политики: магазин с чеком в три тысячи рублей и обменник с чеком в два миллиона примут разные решения при одном и том же балле. Опираться удобнее на уровень severity, а не на число, и фиксировать пороги письменно.

Значит ли высокий риск, что деньги преступные?

Нет. Сервис показывает связи адреса с рисковыми категориями контрагентов, а не устанавливает факт преступления и не даёт юридическую квалификацию. Решение по платежу принимает комплаенс-офицер: ответ API — это материал для решения и доказательство того, что проверка была сделана.

Какие блокчейны поддерживаются?

Двенадцать сетей: Bitcoin, Ethereum, BNB Smart Chain, Polygon, Tron, Solana, Litecoin, XRP Ledger, Bitcoin Cash, Dogecoin, а также USDT и USDC. Сеть указывается в запросе явно; если её нет в списке, сервис вернёт ошибку 400, а не пустой результат.

Есть ли бесплатный лимит у AML-скоринга?

Нет. Каждый скрининг тарифицируется с первого запроса — за ним стоит платный вызов внешнего источника. Подписки при этом нет: платите ровно за сделанные проверки. Некорректный ввод (ошибка 400) и недоступность источника (ошибка 503) не списываются.

Сервисы из этой статьи

AML-скоринг

AML-скрининг крипто-кошелька: балл риска, санкции, источники и назначение средств, контрагенты

Попробовать прямо сейчас — без регистрации

Демо-ключ ak_sandbox_demo_mockdata_v1 — публичный и общий для всех. С ним API отвечает моками: данные правдоподобные, но сгенерированные, и они не меняются от запроса к запросу — на них удобно писать тесты. Настоящие данные приходят с личным ключом.

curl -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     https://atlorium.com/openapi/aml_ru.json
Полезно? Перешлите коллеге: Telegram VK

Восстанавливаем соединение…

Похоже, связь с сервером ненадолго прервалась. Переподключаемся автоматически — пожалуйста, подождите несколько секунд.

Не удалось переподключиться

Проверьте интернет-соединение. Можно повторить попытку или обновить страницу.

Сессия устарела

Соединение восстановлено, но сессию нужно перезагрузить. Обновите страницу, чтобы продолжить.