DEV Community

OpenClaw Cash
OpenClaw Cash

Posted on Originally published at openclawcash.com

Watch an Agent Payment Land Live, and Trigger Your Own Server

A wallet that can move money is only half useful if your backend cannot tell when the money moved. Until this release an OpenClawCash wallet could pay, swap and bridge, but learning about a payment meant polling, or watching a dashboard that refreshed on a slow cycle.

Two changes fix that: wallet balances now update themselves the moment a transaction is recorded, and wallets can fire a signed webhook to an endpoint you control. This post wires both into one worked example. An agent pays a supplier, you watch the balance move without touching the browser, and your server marks the invoice paid the second the payment confirms.

Every wallet id, address, hash and secret below is a placeholder, so copy the shape, not the values.

What changed

Live balances. The wallet cards on the Wallets page, the wallet detail page, the token balances there, and the ETH, POL and SOL totals on the main dashboard now update themselves when a transaction on that wallet is recorded. No refresh. The number flashes green when it goes up and red when it goes down. Updates arrive within a few seconds, hide balances keeps the figure masked, and the flash respects your reduced motion setting.

Wallet webhooks. wallet.transaction.confirmed fires when a transaction is recorded on one of your wallets, including payments your agents make through the API. Wallet events are their own group: you subscribe to them by name, and * still covers checkout (escrow) events only.

Step 1: Create the webhook

One curl, run once. The response carries a one-time secret that starts with whsec_, so store it in your secret store, not your repo.

curl -X POST https://openclawcash.com/api/agent/checkout/webhooks \
  -H "Content-Type: application/json" \
  -H "X-Agent-Key: occ_your_api_key" \
  -H "Idempotency-Key: webhook-create-001" \
  -d '{
    "url": "https://example.com/occ-webhook",
    "eventTypes": ["wallet.transaction.confirmed"],
    "enabled": true
  }'
Enter fullscreen mode Exit fullscreen mode
{
  "publicId": "wh_a1b2c3d4e5f6",
  "url": "https://example.com/occ-webhook",
  "enabled": true,
  "eventTypes": ["wallet.transaction.confirmed"],
  "secret": "whsec_..."
}
Enter fullscreen mode Exit fullscreen mode

You can also manage endpoints by hand on the wallet webhooks page at https://openclawcash.com/webhooks, where the wallet event types are opt in.

Step 2: The agent sends the payment

Nothing new here, this is the transfer call you already make.

curl -X POST https://openclawcash.com/api/agent/transfer \
  -H "Content-Type: application/json" \
  -H "X-Agent-Key: occ_your_api_key" \
  -d '{
    "chain": "evm",
    "walletId": "Q7X2K9P",
    "to": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
    "amountDisplay": "0.01"
  }'
Enter fullscreen mode Exit fullscreen mode

Step 3: Watch it land

Open https://openclawcash.com/wallets. The card for Q7X2K9P is showing an old balance. A few seconds after the transfer is recorded, the number changes by itself and flashes red, because the balance went down. No refresh, no reconnect.

Step 4: Verify what your server receives

The delivery is a JSON POST whose body looks like this:

{
  "eventId": "evt_...",
  "eventType": "wallet.transaction.confirmed",
  "createdAt": "...",
  "data": {
    "walletId": "Q7X2K9P",
    "walletAddress": "0x...",
    "network": "sepolia",
    "transactionId": 7,
    "type": "transfer",
    "status": "confirmed",
    "direction": "outgoing",
    "hash": "0x...",
    "from": "0x...",
    "to": "0x...",
    "value": "1000000000000000",
    "fee": "0",
    "platformFee": "0"
  }
}
Enter fullscreen mode Exit fullscreen mode

value and the fees are strings in base units. Every delivery carries webhook-id, webhook-timestamp and webhook-signature. The signature is v1,<base64>, an HMAC-SHA256 over {id}.{timestamp}.{raw body} keyed with the base64 decoded secret after whsec_. Reject timestamps older than 5 minutes, and verify against the raw body before any JSON parser runs:

import crypto from "node:crypto";

function verifyWebhook(secret, headers, rawBody) {
  const id = headers["webhook-id"];
  const timestamp = Number(headers["webhook-timestamp"]);
  if (!id || !Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = crypto.createHmac("sha256", key).update(id + "." + timestamp + "." + rawBody).digest("base64");
  return String(headers["webhook-signature"] || "").split(" ").some((entry) => {
    const given = Buffer.from(entry.split(",")[1] || "");
    const want = Buffer.from(expected);
    return given.length === want.length && crypto.timingSafeEqual(given, want);
  });
}
Enter fullscreen mode Exit fullscreen mode

Answer 2xx within 10 seconds, then do your own work. De-duplicate on webhook-id, because a delivery can arrive more than once.

When your backend is down

Nothing is lost. A delivery that does not get a 2xx is retried with growing delays for about a day, and a 410 response disables the endpoint instead of retrying it. The older x-occ-signature header is still sent, so receivers written before this release keep working.

Try it

Point the example at Sepolia, a supported test network, and you can walk the whole path with test funds: create the webhook, send a transfer, watch the Wallets page, and confirm the delivery reaches your own server before you point it at anything real. Checkout webhooks use the same signed format, so this receiver verifies those deliveries too.

Docs: https://openclawcash.com/docs
Changelog: https://openclawcash.com/changelog

Top comments (0)