Skip to main content
LearnGuidesSecure your agent wallet
GUIDE

Secure your agent wallet before going live.

15 minutes
SHORT ANSWER

Going to production with agent payments is not the same checklist as a normal API integration. Add: a deliberate wallet-type choice, a tuned spend policy (not defaults), API and webhook secret rotation on a stated cadence, a weekly audit-log review, and an incident-response runbook that has been drilled at least once. Without these, you ship and learn the hard way.

PREREQUISITES

Before you start.

  • A working agent integration end-to-end on test - see add-payments-to-agent and test-without-real-money.
  • Spend controls configured - see spend controls (this guide assumes a policy is already in place).
  • A secret manager (AWS Secrets Manager, GCP Secret Manager, Vault, 1Password, etc) - secrets in .env files are not enough.
  • A clearly identified on-call engineer with admin access to the wallet dashboard.
  • 15 minutes of focused time. The checklist below is not aspirational - work through it line by line.
STEP 1 OF 4

Run the pre-launch checklist.

Eleven items. Each one is a yes/no question with a single owner. If any item is unchecked, do not go live - work that item to closure first. The cost of a missed item in production is always larger than the cost of finishing it now.

# Pre-launch security checklist for agent wallets.

[ ] Wallet type chosen with reasoned trade-offs (smart wallet vs EOA).
[ ] Spend permission set in the dashboard: an allowance per period + a per-transaction cap.
[ ] API keys scoped to the minimum the agent needs (read_wallet_metadata / pay_bills / receive_money).
[ ] All API keys stored in a secret manager (not env files committed to git).
[ ] Test keys and live keys cannot be swapped (boot-time prefix check in place).
[ ] Webhook secret rotated within the last 90 days (webhooks.rotateSecret).
[ ] Webhook URL pointed at production, not a tunnel.
[ ] Audit review scheduled; alerting in place for unusual events.
[ ] A spike of rejected payments triggers an alert (>5/hour from one agent).
[ ] Incident runbook exists, has been read by someone other than the author.
[ ] One person other than you knows how to revoke an API key in < 5 minutes.
STEP 2 OF 4

Schedule API key and webhook secret rotation.

Quarterly is the default cadence; rotate immediately on any of: suspected leak, lead engineer departure, security incident elsewhere in the company. The two-key window pattern lets you swap keys with zero downtime: create the new key, deploy it everywhere, verify, then revoke the old one.

TypeScript
// One-time key rotation script. Run quarterly, on lead departure, on suspected leak.
import { createClient } from "@blockchain0x/node";

const client = createClient({ apiKey: process.env.BLOCKCHAIN0X_API_KEY! });

async function rotate() {
  // Scope the new key to only what the agent needs.
  const fresh = await client.apiKeys.create({
    name: `prod-${new Date().toISOString().slice(0, 10)}`,
    scopes: ["read_wallet_metadata", "pay_bills", "receive_money"],
  });
  console.log("NEW KEY:", fresh.secret);            // Store in secret manager NOW.

  // Wait until the new key is deployed and verified working, THEN:
  // await client.apiKeys.revoke(OLD_KEY_ID);   // (or apiKeys.rotate in one call)
}
Python
# One-time key rotation script. Run quarterly, on lead departure, on suspected leak.
from blockchain0x import Client
import datetime, os

client = Client()  # reads BLOCKCHAIN0X_API_KEY

def rotate():
    fresh = client.api_keys.create(body={
        "name": f"prod-{datetime.date.today().isoformat()}",
        "scopes": ["read_wallet_metadata", "pay_bills", "receive_money"],
    })
    print("NEW KEY:", fresh["secret"])   # Store in secret manager NOW.

    # Wait until new key is deployed and verified, THEN:
    # client.api_keys.revoke(OLD_KEY_ID)   # (or api_keys.rotate in one call)

Webhook signing secrets rotate similarly through the dashboard - enable a second secret, redeploy the handler to accept either, then retire the old one. Both at quarterly cadence with calendar reminders.

STEP 3 OF 4

Set up the weekly audit review.

Three things to look at every week: new counterparties (anything not seen before deserves a 30-second sanity check), rejected payments (the spend permission did its job - did the agent get stuck somewhere?), and off-hours payments (a payment at 3am from an agent that nominally only runs business hours is the kind of signal that catches injection attacks early).

TypeScript
// Weekly audit query over the webhook events YOU persisted (payment.received /
// payment.sent). There is no transactions.list API - your own store is the log.
const since = Date.now() - 7 * 24 * 60 * 60 * 1000;
const events = await db.paymentEvents.findMany({ where: { receivedAt: { gte: since } } });

const newCounterparties = events.filter((e) => e.counterpartyFirstSeen);
const offHours = events.filter((e) => {
  const h = new Date(e.receivedAt).getUTCHours();
  return h < 5 || h > 22;
});

// Reconcile anything that looks off against the chain:
// const tx = await client.transactions.get(event.txHash);
console.log({ newCounterparties, offHours });
Python
from datetime import datetime, timedelta, timezone

# Query your own persisted webhook events - the SDK has transactions.get(id),
# not a list endpoint, so your event store is the audit log.
since = datetime.now(timezone.utc) - timedelta(days=7)
events = db.payment_events.where(received_at__gte=since)

new_counterparties = [e for e in events if e.counterparty_first_seen]
off_hours = [e for e in events if not (5 <= e.received_at.hour <= 22)]

# Reconcile against the chain when something looks off:
# tx = client.transactions.get(event.tx_hash)
print({"new_counterparties": new_counterparties, "off_hours": off_hours})
STEP 4 OF 4

Write (and drill) the incident runbook.

A runbook is only as good as how recently it was tested. Below is the template - copy it into your team's wiki, edit for your specifics, and schedule a 30-minute drill in the next quarter. Time it; fix whatever the drill reveals as missing.

# Incident response runbook - Agent wallet compromise (suspected or confirmed)

## Trigger
Any of:
- Unrecognised counterparty receiving > $10 USDC.
- > 50 rejected payment attempts from one agent in an hour.
- Engineer reports they cannot account for a recent payment.
- A leaked API key surfaces in a public scan.

## Step 1 - Stop the bleeding (< 5 minutes)
- Revoke the suspect API key: dashboard, or apiKeys.revoke / apiKeys.rotate.
- Revoke the agent's spend permission in the dashboard (allowance to zero).
- A revoked key cannot move funds and a revoked permission authorizes nothing.

## Step 2 - Preserve evidence (< 15 minutes)
- Pull the last 24h from the dashboard activity log and your stored events.
- Note the agent's spend permission as it was at the time of the incident.
- Note the timestamps of any unrecognised payments and their txHash on Base
  (fetch with transactions.get).

## Step 3 - Communicate (< 30 minutes)
- Inform the on-call lead and finance.
- If customer funds are affected, draft a notification template (do not send
  until investigation is far enough along to be accurate).

## Step 4 - Root cause and remediation (24-72 hours)
- Determine: leaked key, compromised webhook secret, prompt-injection
  bypass, integration bug, or other.
- Rotate every secret that could have been exposed.
- Tighten the spend permission (lower the allowance or per-transaction cap).
- File a post-mortem with timeline, RCA, and follow-ups.
COMMON PITFALLS

Five things that catch teams in their first production incident.

Treating a new agent as 'just like the old one'

Every agent gets its own profile, its own keys, its own spend policy. If you reuse keys across agents 'because they are similar', a single key compromise blasts every agent at once. Spinning up a new agent profile takes one API call; the per-agent isolation is worth the minute it costs to do it correctly.

Spend permission sized by guess, not by data

Pick the allowance and per-transaction cap off a guess and one of two things happens: too low and the agent hits the limit mid-day every day while you debug 'payment rejected'; too high and the control is theatre. Within the first week, look at actual daily spend and set the allowance at 2-3x that - not 1x (too tight) and not 100x (effectively unlimited). It is a dashboard change, so retuning is cheap.

Webhook signing secret never rotated

An incident that leaks the webhook secret means an attacker can forge webhooks that look like legitimate payment.received events, which can trick your handler into delivering work that was never paid for. Rotate the secret (webhooks.rotateSecret) on the same cadence as API keys (quarterly) or any time you suspect exposure, and always verify deliveries with webhooks.verify against the current secret.

Audit logs that nobody reads

An audit log only catches things if someone looks at it. Most teams enable the log, never look at it, and find out about issues from customer complaints. Schedule a weekly 15-minute review: new counterparties, rejected payments, off-hours payments. The cost is tiny; the time to detect drops from weeks to days.

No drill on the incident runbook

A runbook that has never been exercised is fiction. Pick a quarterly date, simulate a leaked-key scenario, time how long it takes to revoke and contain. The first drill always reveals something missing (the engineer who knows how to rotate is on vacation, the runbook is behind an expired Notion link). Better to find that in a drill than at 3am during a real incident.

NEXT STEPS

After the security pass.

Security is never done, but the rest of the operational stack benefits from a clean baseline. Tighten spend controls as actual usage data accumulates, harden webhook handling against load, and verify agent identity so counterparties trust your public profile.

Full reference at docs.blockchain0x.com. Related glossary: Coinbase Smart Wallet. Product surface: Agent wallets.

Last reviewed: 2026-05-15. Published under CC BY 4.0.

Ship knowing the wallet is hardened.

Pre-flight checklist, rotation cadence, runbook drilled. Free to start.