跳转到主要内容

交易筛查与持续监控

法币交易筛查、Web3 交易筛查与持续客户监控。

交易筛查

提交交易数据进行 AML 风险评估。使用 eventType: "TRANSACTION_EVENT"。

TRANSACTION_EVENT 用于法币交易筛查(银行转账、支付等链下交易)。如需筛查区块链钱包地址或链上交易,请改用 WEB3_TRANSACTION_EVENT,见 Web3 交易筛查。

接口: workflow.apply.submit POST

请求

{
  "eventType": "TRANSACTION_EVENT",
  "source": "API",
  "data": {
    "clientTransactionId": "TXN-20260416-001",
    "transactionType": "WITHDRAW",
    "amount": 199999.98,
    "transactionCurrencyId": "SGD",
    "altCurrencyId": "SGD",
    "amountInAltCurrency": 199999.99,
    "customerId": "C_abc123",
    "customerType": 1,
    "cpAId": "SENDER_001",
    "cpABank": "Standard Chartered",
    "cpACountry": "SINGAPORE",
    "cpBId": null,
    "cpBBank": null,
    "cpBCountry": null,
    "transactionTimestamp": "2026-04-16T10:30:00Z"
  }
}

异步响应

立即返回 executionId。轮询 workflow.execution.status 获取最终结果 — caseStatus / riskScore 的读法见 工作流生命周期 API → 查询状态与审核结论。

{
  "apiCode": 200,
  "data": {
    "executionId": "exec-7a3f1b2c4d5e6f78",
    "requestId": "req-1776243366618",
    "status": "RUNNING"
  }
}

同步响应

等待工作流完成后内联返回决策结果:

{
  "apiCode": 200,
  "data": {
    "executionId": "exec-7a3f1b2c4d5e6f78",
    "status": "COMPLETED",
    "durationMs": 12,
    "output": {
      "action": "approve",
      "code": 100000
    }
  }
}
字段 描述
status COMPLETED = 已决策, TIMEOUT = 超时, FAILED = 错误
durationMs 处理耗时(毫秒)
output.action approve / reject / review
output.code 决策码(默认: approve=100000, reject=100010; 可配置)

Web3 交易筛查

提交一笔 Web3(链上)交易事件,基于钱包地址(walletAddress)或交易哈希(txHash)进行区块链 KYT(Know Your Transaction,交易/地址风险筛查)筛查 — 二者至少传一个,也可同时传,响应中返回筛查结果与决策。使用 eventType: "WEB3_TRANSACTION_EVENT"。

接口: workflow.apply.submit POST

Web3 筛查与其余 OpenAPI 接口共用同一网关基础地址、同一套 query-string 请求信封(api / apiVersion / apiKey / sign)加 x-domain 请求头,以及同样的请求体 HMAC-SHA256 签名。基础地址与请求格式见 OpenAPI 参考 → 快速入门,完整签名规则见身份验证章节。

workflow.apply.submit 支持 apiVersion 2.0.0,可选传入 workflowId 以指定已发布工作流。workflowId 留空时按 eventType + source 自动路由。若租户存在多个已发布的 Web3 工作流,请在对接前确认要调用的工作流。

提交后轮询 workflow.execution.status,Case 结论(caseStatus / riskScore)的读法见 工作流生命周期 API → 查询状态与审核结论。

请求

方式一 — 只传 walletAddress

{
  "eventType": "WEB3_TRANSACTION_EVENT",
  "source": "API",
  "data": {
    "chain": "TRON",
    "token": "USDT",
    "walletAddress": "TZ73Ue3KUP4PZwZJoj9v71skBShdCXksSw",
    "customerId": "demo-customer-001",
    "clientTransactionId": "tx-demo-001",
    "amount": 1000,
    "transactionCurrencyId": "USDT"
  }
}

方式二 — 只传 txHash

{
  "eventType": "WEB3_TRANSACTION_EVENT",
  "source": "API",
  "data": {
    "chain": "TRON",
    "token": "USDT",
    "txHash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
    "customerId": "demo-customer-001",
    "clientTransactionId": "tx-demo-001",
    "amount": 1000,
    "transactionCurrencyId": "USDT"
  }
}

方式三 — 同时传 walletAddress 与 txHash

{
  "eventType": "WEB3_TRANSACTION_EVENT",
  "source": "API",
  "data": {
    "chain": "TRON",
    "token": "USDT",
    "walletAddress": "TZ73Ue3KUP4PZwZJoj9v71skBShdCXksSw",
    "txHash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
    "customerId": "demo-customer-001",
    "clientTransactionId": "tx-demo-001",
    "amount": 1000,
    "transactionCurrencyId": "USDT"
  }
}

顶层字段

字段 类型 必填 说明
eventType String 是 固定值:WEB3_TRANSACTION_EVENT
source String 是 固定值:API
data Object 是 交易数据(见下)
workflowIdString否仅 apiVersion 2.0.0;已发布工作流 ID。留空则自动路由。

data 字段

字段 类型 必填 说明
chain String 是 链标识,如 TRON、ETH、BTC、BSC、POLYGON
token String 是 代币符号,如 USDC、USDT、ETH、BTC
walletAddress String 至少其一 对手方钱包地址
txHash String 至少其一 链上交易哈希
clientTransactionId String 否 您系统中标识本次交易的 ID,用于审计追踪
customerId String 待确认稳定的客户标识。Web3 指南标为可选,通用提交契约则要求必填。工程团队确认 Web3 要求前请提供此字段。
amount Number 否 交易金额(按 transactionCurrencyId 计价)
transactionCurrencyId String 否 货币代码(如 USDC、USDT、USD)
约束
  • walletAddress 与 txHash 至少填一个,也可以同时填写。
  • 同时填写时,二者必须属于同一条链,且与 chain 一致。
  • 响应中 kytScreenResults 数组长度由此决定:单填则为 1 条,同时填则为 2 条。

响应

接口同步返回。HTTP 200 + apiCode: 200 才表示请求被受理;HTTP 4xx/5xx 表示请求未被网关受理,响应体不会包含 data。请求被受理后,业务结果由 data.output.code 区分:

code action 含义 客户处理要点
100000 approve 未命中风险 正常放行
100001 reject 命中风险 按命中流程处理(拉黑、记风险事件等)
100002 reject KYT 筛查未完成,原因见 data.output.data.message 不代表命中风险

响应中两个核心字段:

通过(100000)

{
  "apiCode": 200,
  "apiMsg": "OK",
  "data": {
    "executionId": "exec-7208f03b931e4897",
    "requestId": "req-1780120490039",
    "sync": true,
    "status": "COMPLETED",
    "output": {
      "code": 100000,
      "action": "approve",
      "data": {
        "kytScreenResults": [
          {
            "address": "TZ73Ue3KUP4PZwZJoj9v71skBShdCXksSw",
            "chain_name": "TRON",
            "risk_level": "low",
            "risk_score": 0,
            "is_detected": false
          }
        ]
      }
    }
  }
}

命中(100001)

{
  "apiCode": 200,
  "apiMsg": "OK",
  "data": {
    "executionId": "exec-8e7624be1d8d439a",
    "requestId": "req-1780120982607",
    "sync": true,
    "status": "COMPLETED",
    "output": {
      "code": 100001,
      "action": "reject",
      "data": {
        "kytScreenResults": [
          {
            "address": "TJJLiCdAEN3Ve4aHdgDeUgPnccJki3qCR7",
            "chain_name": "TRON",
            "risk_level": "critical",
            "risk_score": 90,
            "is_detected": true
          }
        ]
      }
    }
  }
}

KYT 筛查未完成(100002)

kytScreenResults 为空数组,data.output.data.message 给出未完成原因(仅在此场景下出现)。

{
  "apiCode": 200,
  "apiMsg": "OK",
  "data": {
    "executionId": "exec-417cec75a77046d6",
    "requestId": "req-1780138193703",
    "sync": true,
    "status": "COMPLETED",
    "output": {
      "code": 100002,
      "action": "reject",
      "data": {
        "message": "Wallet address does not match the specified chain",
        "kytScreenResults": []
      }
    }
  }
}

响应字段

字段 类型 说明
apiCode Number 网关层返回码,200 表示请求被受理;其他值见 OpenAPI 参考的错误码表。
apiMsg String 网关层返回信息
data.executionId String 工作流执行实例的唯一 ID
data.requestId String 单次请求的链路追踪 ID
data.sync Boolean 是否为同步响应,本接口固定 true
data.status String 工作流执行状态,正常完成为 COMPLETED
data.output.action String 决策结果:approve / reject(注意 reject 不一定代表命中,详见上文业务码对照表)
data.output.code Number 业务码,见上文对照表(100000 / 100001 / 100002)
data.output.data.message String 仅当 code = 100002 时返回,说明 KYT 筛查未完成的原因
data.output.data.kytScreenResults Array KYT 链上筛查结果,长度规则见上文(code = 100002 时为空数组)

kytScreenResults[] 字段

字段 类型 说明
address String 被筛查的钱包地址(同时传 walletAddress + txHash 时,每条记录的地址不同,请以此字段区分)
chain_name String 链标识,对应请求中的 data.chain
risk_level String 风险等级,如 low、critical
risk_score Number 风险评分
is_detected Boolean 该地址是否命中风险

代码示例

所有示例都遵循同一原则:先把请求 body 序列化成字符串,再对这个字符串计算签名,然后把同一个字符串作为 HTTP body 发出去。示例 body 对应方式一(只传 walletAddress);如需按方式二或方式三调用,只需替换 body 中的 walletAddress / txHash 字段,其余代码逻辑不变。

Java:

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class Web3TransactionDemo {
    private static final String GATEWAY = "https://api-gateway-platform.width.com/gateway";
    private static final String API_KEY = System.getenv("WIDTH_API_KEY");
    private static final String API_SECRET = System.getenv("WIDTH_API_SECRET");
    private static final String DOMAIN_ID = System.getenv("WIDTH_DOMAIN_ID");

    public static void main(String[] args) throws Exception {
        String body = """
                {"eventType":"WEB3_TRANSACTION_EVENT","source":"API","data":{\
                "chain":"TRON",\
                "token":"USDT",\
                "walletAddress":"TZ73Ue3KUP4PZwZJoj9v71skBShdCXksSw",\
                "customerId":"demo-customer-001",\
                "clientTransactionId":"tx-demo-001",\
                "amount":1000,\
                "transactionCurrencyId":"USDT"}}""";

        String sign = sign(body, API_SECRET);

        String url = GATEWAY + "?api=workflow.apply.submit"
                + "&apiVersion=1.0.0"
                + "&apiKey=" + URLEncoder.encode(API_KEY, StandardCharsets.UTF_8)
                + "&sign=" + sign;

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                .header("Content-Type", "application/json")
                .header("x-domain", DOMAIN_ID)
                .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
                .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
                .send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));

        System.out.println("HTTP " + response.statusCode());
        System.out.println(response.body());
    }

    private static String sign(String body, String secret) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal(body.getBytes(StandardCharsets.UTF_8));
        return HexFormat.of().formatHex(hash);
    }
}

Python:

import hmac
import hashlib
import json
import os
import requests

GATEWAY = "https://api-gateway-platform.width.com/gateway"
API_KEY = os.environ["WIDTH_API_KEY"]
API_SECRET = os.environ["WIDTH_API_SECRET"]
DOMAIN_ID = os.environ["WIDTH_DOMAIN_ID"]

payload = {
    "eventType": "WEB3_TRANSACTION_EVENT",
    "source": "API",
    "data": {
        "chain": "TRON",
        "token": "USDT",
        "walletAddress": "TZ73Ue3KUP4PZwZJoj9v71skBShdCXksSw",
        "customerId": "demo-customer-001",
        "clientTransactionId": "tx-demo-001",
        "amount": 1000,
        "transactionCurrencyId": "USDT",
    },
}

# Serialize once. Sign and send use the same bytes.
body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False)

sign = hmac.new(
    API_SECRET.encode("utf-8"),
    body.encode("utf-8"),
    hashlib.sha256,
).hexdigest()

response = requests.post(
    GATEWAY,
    params={
        "api": "workflow.apply.submit",
        "apiVersion": "1.0.0",
        "apiKey": API_KEY,
        "sign": sign,
    },
    headers={
        "Content-Type": "application/json",
        "x-domain": DOMAIN_ID,
    },
    data=body.encode("utf-8"),  # same bytes used when signing
)

print(response.status_code, response.text)

Node.js:

const crypto = require('crypto');

const GATEWAY = 'https://api-gateway-platform.width.com/gateway';
const API_KEY = process.env.WIDTH_API_KEY;
const API_SECRET = process.env.WIDTH_API_SECRET;
const DOMAIN_ID = process.env.WIDTH_DOMAIN_ID;

const payload = {
  eventType: 'WEB3_TRANSACTION_EVENT',
  source: 'API',
  data: {
    chain: 'TRON',
    token: 'USDT',
    walletAddress: 'TZ73Ue3KUP4PZwZJoj9v71skBShdCXksSw',
    customerId: 'demo-customer-001',
    clientTransactionId: 'tx-demo-001',
    amount: 1000,
    transactionCurrencyId: 'USDT',
  },
};

// Serialize once. Sign and send use the same bytes.
const body = JSON.stringify(payload);

const sign = crypto
  .createHmac('sha256', API_SECRET)
  .update(body, 'utf8')
  .digest('hex');

const url = new URL(GATEWAY);
url.searchParams.set('api', 'workflow.apply.submit');
url.searchParams.set('apiVersion', '1.0.0');
url.searchParams.set('apiKey', API_KEY);
url.searchParams.set('sign', sign);

(async () => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-domain': DOMAIN_ID,
    },
    body,
  });
  console.log(response.status, await response.text());
})();

错误码

网关错误使用 apiCode:999001 表示签名错误;999302 表示 API 名称或版本无效;999305 表示 API key 无效或停用;999401 表示服务超时。重试前先修正凭证或签名;若 submit 可能已被受理,先做恢复查询。业务结果使用 data.output.code:100000(未命中风险)、100001(命中风险)、100002(KYT 筛查未完成)。不得将 100002 视为已完成且无风险的筛查。

网关层错误码(apiCode,9999xx 系列)在 OpenAPI 参考 → 错误码统一列出,适用于所有接口;业务层结果码(data.output.code,10000x 系列)是本接口专属,见上文 响应 顶部的对照表。

常见问题

Q:签名总是不通过,应该先查什么?

A:99% 的 SIGN_ERROR 都是因为「签名用的 body 字节」与「实际发送的 body 字节」不一致。请确保:构造 JSON 字符串 → 对它计算签名 → 把同一个字符串作为 HTTP body 发送。签名与发送之间不要 pretty-print、不要再调一次序列化、不要补尾部换行。

Q:请求 body 必须压缩(去空格)吗?

A:不需要。body 里可以保留任意换行/缩进,只要您签名时用的字节与发送时用的字节完全相同即可。

Q:交易还没上链,能先筛查这个地址有没有风险吗?

A:可以。使用方式一 — 只传 walletAddress、不传 txHash。筛查以地址本身为对象,不要求交易已经上链:可先筛查对手方地址,再决定是否发起转账。

Web3 customerId 是否必填仍待工程团队确认。请求中请保留稳定的 customerId,以便恢复查询使用相同的 eventType、source 和 customerId。上方示例均包含该字段。

持续监控

对存量客户的异步持续审查。始终为异步模式。使用 eventType: "PERIODIC_FLOW_EVENT"。

接口: workflow.apply.submit POST

请求

{
  "eventType": "PERIODIC_FLOW_EVENT",
  "source": "API",
  "data": {
    "customerId": "C_abc123",
    "customerType": "INDIVIDUAL",
    "name": "John Smith",
    "email": "john@example.com",
    "dateOfBirth": "1990-01-15",
    "countryOfNationality": "SG",
    "idType": "NATIONAL_ID",
    "idNumber": "S9012345A",
    "riskLevel": "LOW",
    "startTime": 1776243366000,
    "lastStartTime": 1776243366000

  }
}

响应

立即返回 executionId。轮询 workflow.execution.status 直至终态;Case 结论(caseStatus / riskScore)的读法见 工作流生命周期 API → 查询状态与审核结论。

{
  "apiCode": 200,
  "data": {
    "executionId": "exec-7a3f1b2c4d5e6f78",
    "requestId": "req-1776243366618",
    "status": "RUNNING"
  }
}

完整字段参考

字段 类型 描述
customerId String 客户唯一标识
customerType String INDIVIDUAL / CORPORATE
name String 全名或公司名称
phone String E.164 格式
email String 客户邮箱
salutation String Mr / Ms / Mrs
aliasName String 别名或商业名称
formerRegisteredName String 曾用注册名称
sex String MALE / FEMALE
dateOfBirth String YYYY-MM-DD
countryOfNationality String ISO 双字母
countryOfResidence String ISO 双字母
countryOfBirth String ISO 双字母
address String 完整地址
idType String NATIONAL_ID / INTERNATIONAL_PASSPORT / DRIVING_LICENCE
idNumber String 证件号码
idIssuedDate String YYYY-MM-DD
idExpiryDate String YYYY-MM-DD
companyName String 仅企业
entityType String 取自配置 avira_entity_types
incorporationNumber String 仅企业
incorporationDate String YYYY-MM-DD, 仅企业
incorporationExpiry String YYYY-MM-DD, 仅企业
countryOfIncorporation String ISO 双字母, 仅企业
countryOfOperations String ISO 双字母, 仅企业
imoNumber String 航运实体的 IMO 编号
isIncorporated Boolean 是否已注册
isRegulatedEntity Boolean 是否受监管
ownershipStructureLayer String 取自配置 ownership_structure_layer
industry String 取自配置 avira_ssic
occupation String 取自配置 avira_ssoc
website String 公司网站 URL
onboardingMode String ONLINE / OFFLINE
paymentMode String BANK_TRANSFER / CASH
productComplexity String 取自配置 product_service_complexity
bankAccounts String 关联银行账户信息
transactionBehaviour String 取自配置 transaction_behaviour
riskLevel String LOW / MEDIUM / HIGH
startTime Long Epoch 毫秒 — 本次审查开始时间
customerCategory String RETAIL / CORPORATE
lastStartTime Long Epoch 毫秒 — 上次审查开始时间
监控事件列表待确认:指南通过 workflow.apply.submit 记录 PERIODIC_FLOW_EVENT,但通用提交 eventType 表未列出它。本监控示例依据持续监控章节;在生产环境启用前,请向工程团队确认租户支持情况及权威的可用取值列表。