Skip to content

charges ​

klap charges create ​

bash
klap charges create --amount <amount> --accept <token:network> --expires-in <seconds> [--fee-payer merchant|payer] [--env test|live]

Thin wrapper over klap.charges.create() in @klappay/node. --amount, --accept, and --expires-in are all required — the API has no "any amount"/"never expires" charge.

Every EVM network (Arc included) can be mixed freely across --accept flags, but tron can't be mixed with any of them — a charge pays out to one split address predicted up front, and TRON's split contract differs from the EVM one, so the API rejects that combination with 400 validation_error.

bash
# $10, USDC on Base, expires in 1 hour
klap charges create --amount 10 --accept USDC:base --expires-in 3600

# accept more than one (token, network) pair — repeat the flag; the payer
# picks whichever they hold
klap charges create \
  --amount 49.90 \
  --accept USDC:base --accept USDT:base --accept USDC:optimism \
  --expires-in 3600

# a charge that expires in a full day
klap charges create --amount 5 --accept USDC:polygon --expires-in 86400

# force a specific environment when both a test and a live key are configured
klap charges create --amount 25 --accept USDC:base --expires-in 1800 --env test

# gross up --amount so the payer covers Klappay's fee instead of you —
# you receive the full $50, not $50 minus the fee
klap charges create --amount 50 --accept USDC:base --expires-in 3600 --fee-payer payer

Output ​

 TEST  — sandbox, no real funds
ch_a1b2c3d4e5
  status:            pending
  settlementStatus:  null
  amount:            49.9
  amountReceived:    null
  fee:               0.499 (1%, paid by merchant)
  merchantAmount:    49.401
  acceptedPayments:  USDC:base, USDT:base, USDC:optimism
  paidWith:          none yet
  address:           0x1234567890abcdef1234567890abcdef12345678
  environment:       test
  checkoutUrl:       https://pay.stage.klappay.com/c/ch_a1b2c3d4e5

A charge created as an escrow also prints an escrow: block at the end (releaserAddress, releasedAt, refundedAt). See Escrow.

  • acceptedPayments — every (token, network) pair you configured.
  • paidWith — every pair actually paid so far, comma-separated, or none yet until the first transfer arrives. A charge with multiple acceptedPayments can be paid with any combination of them; every transfer on an accepted pair sums toward the charge total.
  • fee/merchantAmount — your fee tier's feePercent, the resulting feeAmount, and who's covering it (--fee-payer). merchantAmount is always what actually lands in your payout, regardless of who paid the fee — with --fee-payer merchant (the default) it's amount minus the fee; with --fee-payer payer, amount itself is grossed up so merchantAmount equals the amount you asked for.
  • checkoutUrl — a live link to the hosted checkout page for this exact charge, printed underlined so it's easy to click straight from the terminal. It comes directly from the API response (the CLI never constructs it) and is null if the deployment you're pointed at has no hosted checkout configured. Open it to watch the charge update live as a payer connects a wallet and pays — the same page klap sandbox trigger and klap listen let you drive/observe from the terminal.

--env is required only if both a test and a live key are configured — see Configuration. The LIVE/TEST banner above the charge is printed by every command that resolves an environment, so you're never uncertain which one you're looking at.

Then what? ​

bash
# watch it live as events come in — no webhook or relay setup needed
klap charges watch ch_a1b2c3d4e5

# or drive it yourself, without waiting for a real payment
klap sandbox trigger ch_a1b2c3d4e5 charge.confirmed

See Sandbox and Logs.

klap charges get ​

bash
klap charges get <id> [--env test|live]

Thin wrapper over klap.charges.get(). Prints the charge's current state in the same shape as klap charges create — useful for a charge created elsewhere (your backend, the checkout page) when you just want one snapshot instead of watch's live stream.

klap charges list ​

bash
klap charges list [--status <status>] [--token <token>] [--network <network>] \
  [--since <iso-datetime>] [--overpaid | --no-overpaid] \
  [--limit <n>] [--cursor <cursor>] [--env test|live]

Thin wrapper over klap.charges.list(): one page of charges, one line each — id, status, amount, and createdAt.

bash
# every confirmed charge paid in USDC on Base since October 1st
klap charges list --status confirmed --token USDC --network base --since 2026-10-01T00:00:00Z

# only charges where the payer sent more than the amount
klap charges list --overpaid
 TEST  — sandbox, no real funds
ch_a1b2c3d4e5  confirmed               10 USD  2026-10-12T14:02:11.000Z
ch_f6g7h8i9j0  pending               49.9 USD  2026-10-12T13:58:40.000Z
More results — rerun with the same filters plus --cursor eyJpZCI6ImNoX2Y2...
  • --status — one of pending, partially_paid, confirmed, expired, underpaid.
  • --token/--network — filter on the pair actually paid (paidWith), not on what the charge accepted.
  • --since — an ISO 8601 datetime; filters on createdAt, not on when the status last changed.
  • --overpaid/--no-overpaid — only overpaid charges, or only charges that aren't. Omit both for either.
  • --limit — page size, 1–100 (default 20).
  • --cursor — when there's another page, the last line prints the cursor to fetch it. Pass it back verbatim with the same filters; it's opaque, never build or edit it yourself.

Every flag is validated against @klappay/types's own schema before any request is sent, so a typo'd status or a malformed date fails locally with the flag's name in the error.

klap charges check ​

bash
klap charges check <id> [--tx-hash <hash> --network <network>] [--env test|live]

Thin wrapper over klap.charges.check(): asks the API to look for payments to this charge on-chain right now instead of waiting for its background scan, then prints the result. Use it when a payment was sent but the charge hasn't moved yet.

Pass --tx-hash and --network together (one without the other is rejected) when you already know the paying transaction. The check then costs a single RPC call instead of a block scan, and the output also shows who paid:

bash
klap charges check ch_a1b2c3d4e5 \
  --tx-hash 0x9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0 \
  --network base
 TEST  — sandbox, no real funds
ch_a1b2c3d4e5
  status:            confirmed
  ...
  checkoutUrl:       https://pay.stage.klappay.com/c/ch_a1b2c3d4e5
  payer evidence:
    transactionSender:     0x1111111111111111111111111111111111111111
    tokenSenders:          0x2222222222222222222222222222222222222222
    userOperationSenders:  none
  • transactionSender — whoever signed and submitted the transaction. That's the payer for an ordinary wallet, but it's the relayer or bundler when someone else submitted it for them.
  • tokenSenders — where the paying token transfers actually came from. Covers a wallet whose transaction was relayed (e.g. an EIP-7702 smart account behind a gas-sponsoring relayer). For a swap-to-pay this is the router, not the payer.
  • userOperationSenders — the ERC-4337 smart account whose own user operation paid, when a bundler submitted it.

All three are evidence, not identity: Klappay doesn't decide who the payer is. They're only filled in when the hinted transaction actually paid this charge; otherwise the output says payer evidence: none. It's safe to re-run with the same hint after the charge is confirmed to learn who paid.

If a detected transfer is still short of its network's confirmation depth, the same progress bar as klap charges watch is printed above the charge.

klap charges watch ​

bash
klap charges watch <id> [--env test|live]

Opens a live connection straight to this charge's own event stream and prints its current state every time status/settlementStatus changes, plus a confirmation progress bar while a detected transfer is still waiting on block confirmations. Unlike klap logs --tail, it needs no webhook endpoint or klap listen relay — just the charge id. Runs until the charge reaches a terminal state or you stop it with Ctrl+C.

Not the same tool as klap listen --charge <id>: that relays every raw webhook event dispatched for your org, filtered to one charge, with no confirmation progress bar. See watch vs listen.

 TEST  — sandbox, no real funds
[#######-------------] 33%  base 4/12 blocks
ch_a1b2c3d4e5
  status:            confirmed
  settlementStatus:  pending
  amount:            10
  amountReceived:    10
  fee:               0.1 (1%, paid by merchant)
  merchantAmount:    9.9
  acceptedPayments:  USDC:base
  paidWith:          USDC:base
  address:           0x1234567890abcdef1234567890abcdef12345678
  environment:       test
  checkoutUrl:       https://pay.stage.klappay.com/c/ch_a1b2c3d4e5

Escrow: prepare-release, release, prepare-refund, refund ​

bash
klap charges prepare-release <id> [--out <file>] [--env test|live]
klap charges release <id> --signature <hex> [--env test|live]

klap charges prepare-refund <id> [--out <file>] [--env test|live]
klap charges refund <id> --signature <hex> [--env test|live]

Thin wrappers over klap.charges.prepareRelease()/release() and prepareRefund()/refund(), for a charge created as an escrow. Its funds sit in a dedicated Safe until the escrow's releaser signs one of two transfers:

  • release — the full balance goes to the charge's split address, and the normal distribution pays out from there.
  • refund — the full balance goes back to the address that funded the charge. No distribution follows.

An escrow can only ever be released or refunded, once. Both move real funds on-chain and can't be undone.

The CLI never holds or asks for the releaser's private key. You sign somewhere else (a wallet, viem, ethers, a KMS) and pass the signature in:

  1. Prepare. prepare-release/prepare-refund is read-only and prints the exact Safe transaction the API will execute:

     LIVE  — real funds and real data
    refund — sign as 0x2222222222222222222222222222222222222222
      network:          base (chain id 8453)
      amount:           49.9 USDC (49900000 base units)
      destination:      0x4444444444444444444444444444444444444444
      safeAddress:      0x1111111111111111111111111111111111111111
      tokenAddress:     0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
      nonce:            0
      safeTxHash:       0x5555555555555555555555555555555555555555555555555555555555555555

    Check destination, amount, tokenAddress, the chain id and safeAddress against your own records before signing. Signing commits the escrow's funds to exactly that transfer. For a refund, destination is the sender of the most recently credited transfer, which isn't always the buyer: an exchange withdrawal comes from the exchange's wallet, a swap from a router, and a charge paid by several wallets refunds only to the last one.

  2. Sign. Pass --out tx.json to also write the full transaction, including its EIP-712 typedData, to a file. Sign typedData with the key behind the releaser address. It's plain JSON, so ethers or a KMS can take it as-is. With viem, pass it through toSignableEscrowTypedData from @klappay/types first. The result must be an EIP-712 signature from a plain externally-owned account.

  3. Submit.

    bash
    klap charges refund ch_a1b2c3d4e5 --signature 0x2d0fbf1dba2878...

    The CLI checks that --signature is hex before sending it. The Safe contract itself verifies it on-chain before anything moves. The updated charge is printed, with an escrow block showing releasedAt/refundedAt. A charge.escrow_released or charge.escrow_refunded webhook follows once the transfer executes.

A signature is only valid while the escrow's balance is unchanged. If more funds arrive between prepare and submit, the API rejects with escrow_invalid_signature; prepare again and re-sign. Other errors come straight from the API: escrow_already_released/ escrow_already_refunded, escrow_not_configured, escrow_nothing_to_release/escrow_nothing_to_refund (zero balance), escrow_multiple_payment_pairs, and escrow_safe_mismatch. That last one isn't transient; contact Klappay support. All four commands need an API key with charges:write.

Docs live in ./docs — the source of truth for both the package and this site.