TriportRPC

Wallet watch — event subscriptions

POSThttps://triport.io/v1/wallet/watch

Subscribe 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.

Ethereum, Base, Polygon, BNB Smart Chain, Robinhood Chain (Tron is not monitored)— (tier-gated, see below)Business — 30 rps (wallet_identity), 5 subscriptions × 3 networks, watchlist ≤ 10 000, webhook 10 rps; Enterprise — 150 rps, other limits ×10

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:

TypeTriggerParticipants
contract_deployto == null transaction that left contractAddress in the receiptfrom (deployer), contract
smart_wallet_deployAccountDeployed from EntryPoint v0.6/0.7/0.8, ProxyCreation from Safe ProxyFactory 1.3.0/1.4.1, Coinbase Smart Wallet factory callsfrom, to (new wallet), factory, kind
bridge_outAcross FundsDeposited, Stargate v2 OFTSent, deBridge CreatedOrder, Polygon PoS LockedERC20, Base/Robinhood L1 deposit initiations, transfers to a Relay solverfrom, to (bridge), recipient (+ differs_from_sender), bridge.match_key
bridge_inAcross FilledRelay, Stargate OFTReceived, deBridge FulfilledOrder, Base L2 DepositFinalized, Robinhood L2 gateway, Polygon ExitedERC20, transfers from a solverfrom, to, recipient, bridge.match_key
fresh_fundingNative value or ERC-20 Transfer to an address with nonce 0, no code and zero balance before the blockfrom, to (fresh wallet), amount
delegation_setEIP-7702 type-4 transactionfrom, delegate
reorg_revertChain reorganisation removed previously delivered blocksreverted.from_blockto_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)

namestringrequired
Up to 128 characters.
chainsstring[]required
Subset of eth,base,polygon,bsc,robinhood monitored on this PoP. Max 3 (Business).
eventsstring[]required
Any of the six event types above.
filterobjectoptional
Filter; fresh_wallet is allowed here. Evaluated per participant — the event matches if any participant passes.
enrichstring[]optional
Profile groups attached to participants: names,balances,activity,labels,deployer.
watchliststring[]optional
Only events where a participant (from / to / recipient / deployed contract) is in the list. Max 10 000 (Business).
min_amount_usdnumberoptional
Bridge events only; needs a known USD price (stablecoins always priced).
deliveryobjectrequired
{"type":"webhook","url":"https://…","secret":"…"}, {"type":"ws"} or {"type":"pull"}.
enabledbooleanoptional
Default true; 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
Subscription id — the ?id= of the WebSocket and the path segment of the cursor.
delivery.secretstring
Always masked (***) in responses.
seq (event)integer
Monotonic per PoP; store the last seen value to resume via after_seq / after.
bridge.match_key (event)string
Pair bridge_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)boolean
The bridge recipient is not the depositor.
enrich (event)string
done or skipped (enrichment cap reached).

Errors

CodeMeaningWhen it happens
400invalid_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_limitValidation failed; the message names the field. Webhook URLs must be https://.
401account_requiredThe API key is a static token without an account; create a key in the dashboard.
403tier_insufficient / subscription_limitTier below Business, or the per-tier subscription cap is reached.
404not_foundNo such subscription for this account.
429rate_limitedwallet_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_seq in 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_revert before 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_usd applies 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.