Merchant SDK
@curless/agentbank-merchant-sdk is the entire gateway integration for a merchant
backend. You price orders from your own catalog and open a checkout; the gateway holds
no catalog of yours.
npm install @curless/agentbank-merchant-sdkimport { AgentbankMerchant } from '@curless/agentbank-merchant-sdk';
const agentbank = new AgentbankMerchant({ baseUrl: 'https://mcp.curless.ai', apiKey: process.env.AGENTBANK_MERCHANT_TOKEN!, merchantId: process.env.AGENTBANK_MERCHANT_ID!,});Open a checkout
Section titled “Open a checkout”| Method | Protocol / rail |
|---|---|
checkout.openACP({ currency, items }) |
ACP — card |
checkout.openUCP({ currency, items }) |
UCP — card or stablecoin |
checkout.openX402({ amount, items, currency? }) |
x402 — stablecoin (USDC/RLUSD) |
checkout.chargeSkyfire({ token, description? }) |
Skyfire — charge a KYAPay token |
checkout.openMPP({ amount, currency, method?, credential?, description? }) |
MPP — HTTP-402, incl. Tempo on-chain |
items are { sku, name, quantity, unitPrice } in minor units — for ACP/UCP you set
unitPrice and the total is derived. openX402 and openMPP instead take an
authoritative top-level amount (the item list is descriptive). openMPP with no
credential returns the 402 challenge; call again with the buyer’s credential to settle.
Read your money
Section titled “Read your money”orders.list({ status, protocol, currency, from, to, limit, offset })— filtered + paginated, with a total.orders.get(id)— one order in full (line items + how it was paid).orders.summary({ … })— count + gross by currency + breakdowns.balanceTransactions.list({ limit, offset })— a paginated fund-flow statement (your live per-currency wallet balance is in the MCP connector’sget_balance).
Refunds
Section titled “Refunds”orders.listRefundRequests({ status }), orders.approveRefund(id),
orders.rejectRefund(id, { note }), and orders.refund(orderId, { amount, reason })
(merchant-initiated). Approve forwards the refund to Curless, which executes it.
Webhooks
Section titled “Webhooks”Register an endpoint (POST /v1/webhook-endpoints, returns a whsec_ once) and verify
deliveries — HMAC-SHA256, constant-time, edge-safe (Web Crypto). verifyWebhook(secret, rawBody, signature) returns a boolean; parseVerifiedWebhook(secret, rawBody, signature)
verifies and returns the parsed event (or throws).
The list of event types an endpoint can receive is in the API reference → Webhook events.
import { parseVerifiedWebhook } from '@curless/agentbank-merchant-sdk';const event = await parseVerifiedWebhook(whsec, rawBody, signatureHeader);Deliveries are at-least-once — dedupe on event.id
Section titled “Deliveries are at-least-once — dedupe on event.id”A delivery is retried with backoff until your endpoint returns 2xx, so the same event can arrive more than once: if your handler commits and then the response is lost in flight, we never saw the 2xx and will send it again. This is normal, not an error condition — a handler that books a refund or a fulfilment on every delivery will eventually do it twice.
event.id is the dedupe key. It is assigned once, when the event is queued, and
every retry re-sends that stored payload verbatim — so it is stable across retries
and safe to persist:
const event = await parseVerifiedWebhook(whsec, rawBody, signatureHeader);
// Unique index on event_id. Already seen → ack and stop.const fresh = await db.markSeen(event.id);if (!fresh) return new Response('ok', { status: 200 });
await handle(event);return new Response('ok', { status: 200 });Ack duplicates with a 2xx, as above — returning an error re-queues the event you already processed.
One caveat if you register more than one endpoint: a single event fans out to
each of them carrying the same event.id. That is intentional (it is one event),
but if two endpoints feed the same handler, key your dedupe on the endpoint plus
event.id rather than event.id alone — otherwise the second endpoint’s delivery
looks like a duplicate and gets dropped.
Errors
Section titled “Errors”Every non-2xx throws a typed AgentbankError with status + code — catch it to
handle failures uniformly.

