跳轉到主要內容

交易篩查與持續監控

法幣交易篩查、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 表未列出它。本監控示例依據持續監控章節;在生產環境啓用前,請向工程團隊確認租戶支持情況及權威的可用取值列表。