Transaction Screening
Submit your users' crypto transactions to Wirex for risk assessment, as required under the External Authorization model.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Getting Started | Platform overview and setup |
| Api Basics | Required headers and request configuration |
| Authentication | How to obtain access tokens |
| Overview | Why balances and transactions live in your system |
Overview
Under the standard integration model, crypto deposits and transfers move through Wirex-managed
wallets, so Wirex screens them for risk automatically as it processes them.
Under External Authorization, your system holds the balances and the transactions never reach Wirex.
Nothing is screened unless you submit it. This endpoint is how you submit it.
POST /api/v1/transactions/risk-assessment
Submitting these transactions is a regulatory requirement of the External Authorization model, not
an optional feature. Wirex is obliged to screen the crypto activity of users onboarded through the
platform; on this model that obligation can only be met with data you provide. Treat the call as part
of your transaction pipeline, not as a reporting job that can be deferred or sampled.
What to Submit
Submit every crypto deposit and every crypto transfer belonging to a user you onboarded through
Wirex, once the transaction is confirmed on-chain.
| Movement | Direction | Submit |
|---|---|---|
| User receives crypto | Inbound | Yes |
| User sends crypto | Outbound | Yes |
| Fiat or card activity | — | No. Card transactions are already visible to Wirex — see Card Transactions |
| Internal ledger movements with no on-chain transaction | — | No. The endpoint requires a real transaction hash |
Submit after confirmation, not on broadcast. The report is attributed to a specific on-chain
transaction, and an unconfirmed hash that never lands leaves a report referencing a transaction that
does not exist.
Request
{
"direction": "Outbound",
"user_address": "0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
"amount": 99.99,
"asset_symbol": "USDC",
"chain_name": "Base",
"transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000001",
"sender_address": "0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
"recipient_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
}| Field | Type | Required | Description |
|---|---|---|---|
direction | string | Yes | Inbound or Outbound. Determines whether the transaction is screened as a deposit or as a transfer |
user_address | string | Yes | Wallet address the report is attributed to — the address of your user, not the counterparty |
amount | number | Yes | Transaction amount in the transaction's own asset. Must be greater than 0 |
asset_symbol | string | Yes | Token or currency symbol, e.g. USDC |
chain_name | string | Yes | Network the transaction settled on. See Supported Networks |
transaction_hash | string | Yes | On-chain transaction hash |
sender_address | string | Yes | Address funds moved from |
recipient_address | string | Yes | Address funds moved to |
user_address is a separate field from sender_address and recipient_address on purpose. It is
whose report this is. On an Outbound transaction it usually equals sender_address; on an
Inbound one it usually equals recipient_address — but the endpoint does not infer it, and a wrong
value files the report against the wrong user.
Address Format
user_address, sender_address and recipient_address are all validated against the same
multi-chain pattern:
^(0x[a-fA-F0-9]{40}|C[A-Za-z2-7]{55}|G[A-Za-z2-7]{55}|T[1-9A-HJ-NP-Za-km-z]{33}|r[1-9A-HJ-NP-Za-km-z]{24,34})$
| Prefix | Chain family | Format |
|---|---|---|
0x | EVM | 40 hex characters |
C / G | Stellar | 55 base32 characters |
T | Tron | 33 base58 characters |
r | XRPL | 24–34 base58 characters |
Bitcoin, Cosmos, Solana and Waves addresses do not match this pattern, even though those networks
appear in chain_name. See Limitations.
Supported Networks
chain_name must be one of the following. Values are case-sensitive and are not the same strings
used elsewhere in the API — send Base, not BASE; Stellar, not STELLAR.
ArbitrumNative | Arbitrum | CChain | CChainBridged |
CChainBridgedBitcoin | CChainBridgedBinance | WrappedCChain | WrappedCChainBridgedEthereum |
BinanceSmartChain | Bitcoin | Ethereum | EthereumBRZ |
WrappedEthereum | Polygon | PolygonBRZ | PolygonBridgedBinance |
WrappedPolygon | Cosmos | Tron | WrappedTron |
Waves | Stellar | Ripple | Solana |
Base |
Anything outside this list is refused with chain_name: {value} and issue: invalid_value.
Response
{}A 200 means the transaction was accepted into the screening pipeline. It is not a risk verdict.
Screening happens after the call returns, and this endpoint does not tell you whether the transaction
was flagged. Do not gate a user action on the response.
Code Examples
await fetch(`${baseUrl}/api/v1/transactions/risk-assessment`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-User-Wallet': userEoaAddress,
'X-Chain-Id': chainId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
direction: 'Outbound',
user_address: userEoaAddress,
amount: 99.99,
asset_symbol: 'USDC',
chain_name: 'Base',
transaction_hash: transactionHash,
sender_address: userEoaAddress,
recipient_address: recipientAddress
})
});requests.post(
f"{base_url}/api/v1/transactions/risk-assessment",
headers={
"Authorization": f"Bearer {access_token}",
"X-User-Wallet": user_eoa_address,
"X-Chain-Id": chain_id,
"Content-Type": "application/json"
},
json={
"direction": "Outbound",
"user_address": user_eoa_address,
"amount": 99.99,
"asset_symbol": "USDC",
"chain_name": "Base",
"transaction_hash": transaction_hash,
"sender_address": user_eoa_address,
"recipient_address": recipient_address
}
)body, _ := json.Marshal(map[string]interface{}{
"direction": "Outbound",
"user_address": userEoaAddress,
"amount": 99.99,
"asset_symbol": "USDC",
"chain_name": "Base",
"transaction_hash": transactionHash,
"sender_address": userEoaAddress,
"recipient_address": recipientAddress,
})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/transactions/risk-assessment", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Wallet", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()Limitations
- The response carries no verdict. Acceptance is not clearance, and a flagged transaction does
not surface here. Screening outcomes are handled through compliance channels, not this endpoint. - No deduplication guarantee. Submitting the same
transaction_hashtwice produces two reports.
Track what you have already sent rather than replaying a window on every run. - No batch endpoint. One call per transaction.
- Address validation is narrower than the network list.
chain_nameaccepts Bitcoin, Cosmos,
Solana and Waves, but the address pattern only matches EVM, Stellar, Tron and XRPL formats — a
Bitcoin transaction cannot currently be submitted with genuine Bitcoin addresses. Raise these with
Wirex rather than substituting a placeholder address. chain_namecasing is its own vocabulary, not the chain names used in the token catalogue or
theX-Chain-Idheader.amountmust be positive. Zero-value transactions are refused, so they cannot be reported.
Error Handling
Validation Errors (400)
| Error Description | field | issue | Resolution |
|---|---|---|---|
direction must be Inbound or Outbound | direction | invalid_value | Send Inbound or Outbound, exact casing |
user address must be a valid address | user_address | invalid_address | Match the address pattern. pattern details carry the regex |
Invalid value for amount | amount | invalid_value | Send an amount greater than 0 |
asset symbol is required | asset_symbol | missing | Send the token symbol |
chain_name must be a supported network | chain_name | invalid_value | Use a value from Supported Networks |
transaction hash is required | transaction_hash | missing | Send the on-chain hash |
sender address must be a valid address | sender_address | invalid_address | Match the address pattern |
recipient address must be a valid address | recipient_address | invalid_address | Match the address pattern |
Example Error Response
{
"error_reason": "ErrorInvalidField",
"error_description": "chain_name must be a supported network",
"error_category": {
"category": "CategoryValidationFailure",
"http_status_code": 400
},
"error_details": [
{ "key": "field", "details": "chain_name" },
{ "key": "issue", "details": "invalid_value" },
{ "key": "chain_name", "details": "BASE" }
]
}Missing fields return ErrorMissingField; malformed ones return ErrorInvalidField. See
Error Reference for the full reason and category lists.
Failures After Validation
A 500 means the transaction did not reach the screening pipeline. Retry with backoff and keep the
transaction in your outbox until a call succeeds — a dropped report is a compliance gap, not a
missing log line.
Updated 20 days ago

