Exploit Wallet Monitoring: Webhooks and Pre-Credit Holds

Exploit Wallet Monitoring: Webhooks and Pre-Credit Holds

Crypto APIs Team

Oct 21, 2026 • 5 min

TL;DR: When an exploiter moves stolen funds toward a mixer, exchange and custody teams can subscribe to address webhooks on the exploiter's wallets. They then screen every inbound deposit with a GET call to /aml/transactions/{blockchain}/{transactionHash} and /aml/addresses/{address}. Any deposit that returns isFlagged: true or a high riskBand goes into a review queue instead of a user balance. This post walks through the subscription setup, the screening calls with real responses, and the decision logic for holding a credit.

The problem

Decrypt reported another exploit in which the attacker routed stolen Ethereum into Tornado Cash. The report covers the movement itself. It does not cover what happens next for an exchange operations engineer or a custody platform's compliance lead.

Funds that enter a mixer come out somewhere. Often they come out through fresh addresses, and some of those addresses eventually send to a deposit address that you issued to a customer. If your crediting pipeline only checks confirmations, that deposit lands in a user balance and can be withdrawn before anyone looks at it.

The build problem has two halves:

  • Know the moment a known exploiter wallet moves funds.
  • Stop any deposit with direct or indirect exposure to those funds from being credited automatically.

Our earlier post, Wallet Drain Detection: Address Webhooks for Custody Teams, covers drains from your own wallets. This post covers the other direction: tainted funds arriving at your wallets.

What you need

  • A Crypto APIs key, sent as the X-API-Key header on every request.
  • A public HTTPS callback endpoint that accepts POST requests and verifies the callback secret you set on each subscription.
  • A watchlist of exploiter addresses. This is the hard part. Addresses come from incident reports, security firm disclosures, and the protocol's own post-mortem. They often arrive hours after the first outflow, and the list grows as the attacker splits funds.
  • A crediting service that can hold a deposit in a pending state. If your ledger only supports credited or not seen, add the pending state first.
  • A human review queue. Screening produces signals. A person still decides whether to freeze, return, or release.

How it works

Step 1: Subscribe to exploiter addresses

Blockchain Events lets you register an address and receive a callback for transactions involving it. For an exploiter wallet on Ethereum, create two subscriptions:

  • A native-coin subscription via POST /blockchain-events/{blockchain}/{network}/address-coins-transactions-unconfirmed. This fires when a transaction appears in the mempool, before confirmation.
  • A token subscription via POST /blockchain-events/{blockchain}/{network}/address-tokens-transactions-confirmed. This catches ERC-20 (Ethereum token standard) movements such as stablecoin transfers that attackers swap into before mixing.

Each subscription returns a referenceId. Store it alongside the watchlist entry so you can list subscriptions with GET /blockchain-events/{blockchain}/{network} and remove stale ones with DELETE /blockchain-events/{blockchain}/{network}/{referenceId}. The exact request body is documented in the monitor addresses with webhooks guide. Callbacks are delivered in under 100 milliseconds from detection.

When a callback arrives for an exploiter address, do two things. Alert the on-call compliance channel. Then add every counterparty address in that transaction to an internal heightened-risk list for the incident.

Step 2: Screen every inbound deposit before crediting

Your existing deposit webhooks already tell you when funds hit a customer address. Insert one screening step between detection and credit. Start with the transaction's counterparties:

GET https://rest.cryptoapis.io/aml/transactions/ethereum/{transactionHash}
X-API-Key: <your key>
{"apiVersion":"2024-12-12","requestId":"6aa3c4a32f6f68835e3dc003","data":{"item":{
  "isFlagged":false,"riskScore":0,"riskBand":"low","flaggedAddresses":[]}}}

Then screen the sending address directly through Verify Address. The address goes in the path. There is no request body.

GET https://rest.cryptoapis.io/aml/addresses/{address}
X-API-Key: <your key>

A flagged result looks like this:

{"apiVersion":"2024-12-12","requestId":"6aa3c4642f6f68835e3dbd6c","data":{"item":{
  "blockchain":"ethereum","isFlagged":true,"riskScore":80,"riskBand":"high",
  "severity":"high","categories":["malicious","sanctions"],
  "sources":[{"provider":"graphsense","label":"tornado.cash","categories":["malicious"],
              "severity":"high","listingTimestamp":1784210009,"sanctionPrograms":[]}],
  "attribution":{"entity":"tornado-cash","type":"mixer","label":"Tornado.Cash: Donate"},
  "listingTimestamp":1784210009}}}

A clean result omits optional fields entirely:

{"apiVersion":"2024-12-12","requestId":"6aa3c48d2f6f68835e3dbf05","data":{"item":{
  "isFlagged":false,"riskScore":0,"riskBand":"low","severity":"none",
  "categories":[],"sources":[]}}}

Step 3: Check indirect exposure

Mixer outputs usually pass through one or more fresh addresses before reaching an exchange. Direct screening of the last hop can come back clean. For deposits above your review threshold, or from senders on the incident's heightened-risk list, call GET /aml/exposure/{blockchain}/{address} to assess indirect exposure. Call GET /aml/exposure/{blockchain}/{address}/paths when a reviewer needs to see the route funds took. Consult the API reference for the response fields of these two endpoints. Do not assume they match the address response.

Step 4: Decide

item = response.data.item

hold = (
  item.isFlagged == true
  or item.riskBand in ["high", "severe"]
  or len(txItem.flaggedAddresses) > 0
  or sender in incident_heightened_risk_list
)

if hold:
  ledger.set_status(deposit_id, "pending_review")
  review_queue.push(deposit_id, item.categories, item.get("attribution"))
else:
  ledger.credit(deposit_id)

Use item.get("attribution"), not direct access. On clean responses the field does not exist.

What to watch for

  • Listing lag. A fresh exploiter address will not be in any provider's dataset at the moment of the exploit. The listingTimestamp tells you when a source labeled the address. Your own watchlist and heightened-risk list cover the gap until labels propagate. Re-screen held and recently credited deposits once labels update.
  • Medium is not low. riskBand takes the values low, medium, high, and severe. Decide explicitly what medium does in your pipeline. A silent default to credit is the usual mistake.
  • Duplicate callbacks. Webhook delivery retries on failure. Key your handler on transaction hash plus subscription referenceId so a retry does not create two review cases or two credits.
  • Unconfirmed events can disappear. An unconfirmed callback on an exploiter wallet is an early warning, not a final state. Never credit on it. For crediting rules, see Deposit Confirmation Thresholds: Per-Chain Crediting Rules.
  • Cross-chain hops. Attackers bridge. An exploit that starts on Ethereum can surface as a deposit on another chain. Run the same screening step on every chain you accept, not only the chain of the original incident.
  • Screening adds latency to crediting. Run the AML calls in parallel with your confirmation wait. The screening result is usually ready before the deposit reaches its threshold.

Exploit response comes down to two calls placed correctly: a webhook on the attacker's wallet and an AML check before every credit. Blockchain Events handles the first, and Verify Address handles the second across 20+ chains. Teams running exchanges and custody providers can start on the free tier with no credit card, then size production volume on the pricing page.

Infrastructure optimized for growth

35+

Networks Supported

25ms

Avg Processing Time

25,000+ rq/s

Enterprise-ready

100+ TB

of Big Data

Related articles

Share