Public API

Webhooks

Subscribe to a payment. When it settles we POST a signed paid webhook and include a free AML screen — the stored AML payload (score, risk_level, detail_list, risk_detail) on event=paid. No report URL. Address Watch uses the same delivery plane. Allowlist egress IPs. Delivery is asynchronous — cabinet Test enqueues work and does not wait for your HTTP response.

webhookpaymentsAMLdeliveryegress

← Public API

Configure the webhook

PUT /v1/address-watch/webhook with url, enabled, and optional events. GET returns url, enabled, events, and egress_ips.

Event filters: payment = non-block hits with direction=in; payout = direction=out; block = type=block. Each type may include payment_networks / payout_networks / block_networks (network slugs). Boolean-only clients still work: true with an empty network list means all networks. Omit events on PUT to keep existing filters (defaults: all event types on, all networks).

egress_ips lists the public source IPs RpcNode uses for outbound POSTs (from server config WEBHOOK_EGRESS_IPS) so you can allowlist them.

Delivery pipeline

Address Watch activity and cabinet Test share the same outbound path. Production hits are consumed from Kafka by core (address-watch:consume-hits); cabinet Test goes through the connect BFF and enqueues a demo payload with bill=false. Redis holds watch indexes and KV (registry, Bloom, cursors) — it is not the messaging path for webhook delivery.

Kafka topics on this path: rpcnode-watch-blocks (ingest → match), rpcnode-watch-hits (match → core), rpcnode-webhooks-deliver (enqueue → Go webhook-deliver), rpcnode-webhooks-result (delivery outcome → core webhooks:consume-results; bill on success when bill=true).

Cabinet Test: pick a subscription (event × network), then receive a queued response immediately — not a sync HTTP round-trip to your URL. Delivery runs asynchronously on webhook workers with bill=false (no credit charge).

Ops: enqueue alone is not enough. If messages sit in rpcnode-webhooks-deliver with no consumer, start backend/webhook-deliver (Go). Then run PHP core webhooks:consume-results (supervisor webhooks-results) to bill/log outcomes. Prerequisites: Kafka up; KAFKA_BROKERS set in webhook-deliver and core.

Service chain + what to run

Address watch / Test (cabinet)
  → connect BFF (test)  OR  core address-watch:consume-hits (prod)
  → Kafka rpcnode-webhooks-deliver
  → backend/webhook-deliver (Go)  HTTP POST + HMAC
  → Kafka rpcnode-webhooks-result  (ok / fail)
  → core webhooks:consume-results  (bill on success when bill=true)

Upstream (prod hits only):
  Go ingest → rpcnode-watch-blocks → Go match → rpcnode-watch-hits
  → core persist + fan-out

# 1) HTTP delivery — required when deliver topic has lag
cd backend/webhook-deliver
# KAFKA_BROKERS=host.docker.internal:9092
go run ./cmd/webhook-deliver
# or: docker compose up -d --build
# supervisor: webhook-deliver ×2

# 2) results billing / log
php artisan webhooks:consume-results
# supervisor (node-core-worker): webhooks-results

Delivery request

Method: POST to your configured webhook.url.

Headers: Content-Type: application/json, X-Timestamp (Unix seconds), X-Signature (hex HMAC for transport integrity — not returned or managed via the API).

Body: flat JSON — no nested payload wrapper.

Address Watch wallet hits: type is payment | payout | other | block (direction on the watched address). Same fields appear on GET /v1/address-watch/events.

Payments product (merchant payment intents): separate payload with product="payments" and type="payment_order" (never type=payment/payout). event is seen | aml_check | paid | failed | underpaid | expired. After on-chain confirm the first webhook is aml_check (funds arrived, AML in progress). After screening the second is paid with the stored AML object (score, risk_level, hacking_event, detail_list, risk_detail, address_label, checked_at). No provider field, no report URL. Includes payment_id, order_id, amount fields, status, and lifecycle. Delivered to payment.webhook_url when set, otherwise to the same Address Watch webhook URL(s).

Example bodies

// Address Watch — payment (incoming wallet hit)
{
  "watch_id": "aw_…",
  "network": "ethereum",
  "address": "0x…",
  "tx_hash": "0x…",
  "block_height": 12345678,
  "type": "payment",
  "direction": "in"
}

// Address Watch — payout (outgoing)
{
  "type": "payout",
  "direction": "out",
  "…": "…"
}

// Address Watch — block
{
  "type": "block",
  "network": "ethereum",
  "block_height": 12345678,
  "block_hash": "0x…",
  "tx_count": 2,
  "transactions": ["0x…", "0x…"]
}

// Payments product — order lifecycle (not Address Watch type=payment)
{
  "product": "payments",
  "type": "payment_order",
  "event": "check",
  "payment_id": "…",
  "order_id": "ord_123",
  "network": "ethereum",
  "address": "0x…",
  "amount": "1.5",
  "amount_raw": "1500000000000000000",
  "status": "check",
  "tx_hash": "0x…",
  "lifecycle": "check"
}

// Payments — confirmed on-chain, AML in progress
{
  "product": "payments",
  "type": "payment_order",
  "event": "aml_check",
  "status": "aml_check",
  "tx_hash": "0x…",
  "lifecycle": "aml_check",
  "aml": null
}

// Payments — AML done (terminal success)
{
  "product": "payments",
  "type": "payment_order",
  "event": "paid",
  "status": "paid",
  "tx_hash": "0x…",
  "lifecycle": "paid",
  "aml": {
    "score": 0,
    "risk_level": "Low",
    "hacking_event": "",
    "detail_list": [],
    "risk_detail": [],
    "address_label": "",
    "checked_at": "2026-08-16T10:00:00+00:00"
  }
}

Allowlist source IPs

Treat egress_ips from GET /v1/address-watch/webhook as the authoritative list of RpcNode outbound addresses.

Allowlist those IPs on your firewall, reverse proxy, or WAF so webhook POSTs are not dropped.

You can also configure the webhook URL and event filters in the RpcNode cabinet under WebHooks.

Tips

  • Respond quickly with 2xx; delivery uses a short HTTP timeout.
  • You can poll GET /v1/address-watch/events as a backup if a delivery fails.
  • Address Watch payload type is payment | payout | other | block (never tx). Payments product uses product=payments and type=payment_order with event=seen|aml_check|paid|failed|underpaid|expired — do not confuse with AW type=payment.
  • Billing: successful deliveries (bill=true) spend the same credit pool as RPC. Address Activity (payment/payout) and Payments (product=payments) use per-environment credit rates — we ingest/match/deliver, so they cost much more than one RPC method. Block webhooks = that network’s full get-block cost. Cabinet Test uses bill=false. Address slots stay a hard plan cap (Free = 0).

Related