Wallet watch — event subscriptions
https://triport.io/v1/wallet/watchSubscribe to what wallets do across the EVM networks Triport monitors: contract and smart-wallet deployments, bridge deposits and fills, funding of fresh wallets, EIP-7702 delegations — every participant enriched with the cross-chain identity profile and matched against your filter, delivered by webhook, WebSocket or a REST cursor.
A subscription names the networks and event types you care about, an
optional filter evaluated against each
participant's profile, the profile groups to attach (enrich), an optional
watchlist of addresses, a USD floor for bridge amounts, and a delivery
channel. Triport's scanner follows every monitored network block by block
(full transactions plus receipts, with a reorg window), detects events,
stores them with a monotonic per-PoP sequence number, and matches them
against all enabled subscriptions.
Event types:
| Type | Trigger | Participants |
|---|---|---|
contract_deploy | to == null transaction that left contractAddress in the receipt | from (deployer), contract |
smart_wallet_deploy | AccountDeployed from EntryPoint v0.6/0.7/0.8, ProxyCreation from Safe ProxyFactory 1.3.0/1.4.1, Coinbase Smart Wallet factory calls | from, to (new wallet), factory, kind |
bridge_out | Across FundsDeposited, Stargate v2 OFTSent, deBridge CreatedOrder, Polygon PoS LockedERC20, Base/Robinhood L1 deposit initiations, transfers to a Relay solver | from, to (bridge), recipient (+ differs_from_sender), bridge.match_key |
bridge_in | Across FilledRelay, Stargate OFTReceived, deBridge FulfilledOrder, Base L2 DepositFinalized, Robinhood L2 gateway, Polygon ExitedERC20, transfers from a solver | from, to, recipient, bridge.match_key |
fresh_funding | Native value or ERC-20 Transfer to an address with nonce 0, no code and zero balance before the block | from, to (fresh wallet), amount |
delegation_set | EIP-7702 type-4 transaction | from, delegate |
reorg_revert | Chain reorganisation removed previously delivered blocks | reverted.from_block…to_block (always delivered) |
Enrichment runs through the same profile cache as the identity endpoint
and is capped per network; when the cap is hit the event is delivered with
enrich: "skipped" to subscriptions without profile predicates and dropped
for subscriptions whose filter needs the profile.
Parameters
Request body (POST, PATCH partial)
namestringrequiredchainsstring[]requiredeth,base,polygon,bsc,robinhood monitored on this PoP. Max 3 (Business).eventsstring[]requiredfilterobjectoptionalfresh_wallet is allowed here. Evaluated per participant — the event matches if any participant passes.enrichstring[]optionalnames,balances,activity,labels,deployer.watchliststring[]optionalmin_amount_usdnumberoptionaldeliveryobjectrequired{"type":"webhook","url":"https://…","secret":"…"}, {"type":"ws"} or {"type":"pull"}.enabledbooleanoptionaltrue; false pauses matching.Response
An event as delivered (webhook body, WS frame, or events[] item of the cursor):
{
"seq": 128417,
"chain": "base",
"block": 51314763,
"block_hash": "0x…",
"tx": "0x…",
"log_index": 210,
"ts": "2026-09-14T22:41:03Z",
"type": "bridge_in",
"bridge": {
"protocol": "across",
"direction": "in",
"src_chain": "eth",
"dest_chain": "base",
"match_key": "across:1846213",
"contract": "0x09aea4b2242abc8bb4bb78d537a67a245a7bec64"
},
"amount": {
"token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"raw": "1500000000",
"value": "1500",
"usd": 1500
},
"from": {
"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"profile": {
"address": "0xd8da…",
"names": [
{
"source": "ens",
"name": "vitalik.eth",
"chain": "eth",
"primary": true,
"verified": true,
"normalized": true
}
],
"chains": {
"eth": {
"balance": "1.2345",
"nonce": 812,
"is_contract": false
}
},
"unresolved": [],
"meta": {
"cached": true,
"generated_at": "…",
"pop": "eu",
"cost": {}
}
}
},
"to": {
"address": "0x1111…"
},
"recipient": {
"address": "0x1111…",
"differs_from_sender": true
},
"enrich": "done"
}iduuid?id= of the WebSocket and the path segment of the cursor.delivery.secretstring***) in responses.seq (event)integerafter_seq / after.bridge.match_key (event)stringbridge_out with the bridge_in on the destination network (across:<depositId>, stargate:<guid>, debridge:<orderId>, robinhood_native:<sequence>); absent where the protocol has no shared id.recipient.differs_from_sender (event)booleanenrich (event)stringdone or skipped (enrichment cap reached).Errors
| Code | Meaning | When it happens |
|---|---|---|
400 | invalid_body / invalid_name / invalid_chains / unknown_chain / too_many_chains / invalid_events / unknown_event / invalid_filter / invalid_enrich / invalid_watchlist / too_many_watchlist / invalid_min_amount_usd / invalid_delivery / invalid_after / invalid_limit | Validation failed; the message names the field. Webhook URLs must be https://. |
401 | account_required | The API key is a static token without an account; create a key in the dashboard. |
403 | tier_insufficient / subscription_limit | Tier below Business, or the per-tier subscription cap is reached. |
404 | not_found | No such subscription for this account. |
429 | rate_limited | wallet_identity sustained RPS exceeded. |
All error bodies use the shared envelope (error, message, request_id).
See errors.md for the full envelope and per-code fields.
Examples
Verify a webhook signature (Node)
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return header?.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Consume the WebSocket with resume (Node)
import WebSocket from "ws";
let last = Number(process.env.LAST_SEQ ?? 0);
function connect() {
const ws = new WebSocket(`wss://triport.io/ws/wallet-watch?id=${SUB_ID}&after_seq=${last}`, {
headers: { Authorization: `Bearer ${process.env.TRIPORT_API_KEY}` },
});
ws.on("message", (raw) => {
const m = JSON.parse(raw);
if (m.type === "resume") { if (!m.buffer_complete) console.warn("gap before", m.after_seq); return; }
last = m.seq;
if (m.type === "reorg_revert") { /* drop state for blocks in m.reverted */ return; }
handle(m);
});
ws.on("close", () => setTimeout(connect, 1000));
}
connect();Poll with the REST cursor (Python)
import os, time, requests
API = f"https://triport.io/v1/wallet/watch/{os.environ['SUB_ID']}/events"
H = {"Authorization": f"Bearer {os.environ['TRIPORT_API_KEY']}"}
after = 0
while True:
page = requests.get(API, params={"after": after, "limit": 200}, headers=H, timeout=30).json()
for ev in page["events"]:
print(ev["seq"], ev["chain"], ev["type"], ev.get("from", {}).get("address"))
after = page["next_after"]
if not page["has_more"]:
time.sleep(5)Fresh wallets funded by a bridge on Robinhood Chain
{
"name": "rh-fresh-from-bridges",
"chains": ["robinhood"],
"events": ["fresh_funding", "bridge_in"],
"filter": {"any": [{"fresh_wallet": true}, {"has_label": ["bridge", "solver"]}]},
"enrich": ["names", "labels"],
"delivery": {"type": "ws"}
}Notes
- Retention: events live 72 hours (or until the 2 GiB per-PoP cap) in
the PoP that observed them;
oldest_seqin the cursor response tells you how far back a replay can go. Subscriptions themselves are shared across PoPs. - Reorgs: the scanner keeps a hash window (12 blocks on Ethereum, 64 on
the L2s and BNB Smart Chain); on a mismatch it deletes the affected events
and delivers
reorg_revertbefore re-scanning the canonical blocks. - Bridge coverage: contracts were confirmed with live logs on both PoPs for Across (all four networks), Stargate v2 (Ethereum, Base), deBridge (Ethereum, Base, Polygon source), Base L2 bridge, Polygon PoS predicate and the Robinhood L1 gateways; pools/gateways without a live log in the probe window (Stargate on Polygon/BSC, deBridge on BSC, Robinhood L2 gateways) are decoded on the same ABI but marked unconfirmed in the bridge ledger.
- USD floors:
min_amount_usdapplies only when the amount is priced — stablecoins are always priced at 1 USD; native coins need a rate, so a floor on unpriced tokens never matches. - Related: profile, batch, filters, streaming overview.