Developers

TapCoin Pay API

Create checkouts, payment links and invoices from your own code, and get a signed webhook when the chain confirms a payment. Base URL https://tapcoinpay.com. All responses are JSON in a { ok, data } envelope.

Quickstart

Three steps from zero to a confirmed payment. About ten minutes.

1. Create an API key

In the dashboard go to Developers and create a key. Keys start with tcp_live_ or tcp_test_. The raw key is shown once; store it as a secret in your environment. Test keys create checkouts on test networks and never match real funds.

2. Create a checkout

Amounts are integer minor units of the fiat currency: 25000 is $250.00. Pass an Idempotency-Key header so a retried request returns the same checkout instead of creating a second one (keys are honored for 24 hours).

curl https://tapcoinpay.com/v1/checkouts \
  -H "Authorization: Bearer tcp_live_…" \
  -H "Idempotency-Key: order_1043" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 25000, "currency": "USD", "description": "Order 1043",
       "assets": ["XRP","RLUSD"], "redirect_url": "https://example.com/thanks",
       "customer_email": "sam@example.com", "metadata": { "order_id": "1043" } }'

Response:

{ "ok": true, "data": {
  "id": "co_3kD9pQx7", "object": "checkout", "status": "created",
  "amount": 25000, "currency": "USD", "description": "Order 1043",
  "assets": ["XRP","RLUSD"],
  "checkout_url": "https://tapcoinpay.com/pay/co_3kD9pQx7",
  "qr_url": "https://tapcoinpay.com/qr/co_3kD9pQx7.svg",
  "expires_at": "2026-09-13T18:45:00Z", "created_at": "2026-09-13T18:30:00Z",
  "metadata": { "order_id": "1043" }
} }

Send the customer to checkout_url. They pick an asset, see the exact amount and address, and pay from their own wallet.

3. Handle the webhook

Register an endpoint under Developers › Webhooks. You receive payment.detected when the transaction appears on the network and payment.confirmed when it reaches the required confirmations. Fulfil the order on payment.confirmed, never on detected. Verify the signature first (see Webhooks).

// Express
app.post("/webhooks/tapcoin", express.raw({ type: "*/*" }), (req, res) => {
  if (!verify(req.body.toString(), req.get("TapCoin-Signature"), process.env.TAPCOIN_WEBHOOK_SECRET))
    return res.status(400).end();
  const evt = JSON.parse(req.body);
  if (evt.event === "payment.confirmed") markOrderPaid(evt.data.checkout.metadata.order_id);
  res.status(200).end();
});

Authentication

Send your key as a bearer token on every request: Authorization: Bearer tcp_live_…. Keys belong to one organization and carry a mode (live or test). Rotate a key from the dashboard at any time; the old key stops working immediately.

Never put a live key in browser code. For browser-side buttons use the embed script, which needs only a public button id.

API reference

All endpoints are under /v1. List endpoints accept ?limit= (max 100) and return next_cursor when there are more results.

MethodPathDescription
POST/v1/checkoutsCreate a checkout. Body: amount, currency, description?, assets?, redirect_url?, customer_email?, metadata?. Honors Idempotency-Key.
GET/v1/checkouts/:idRetrieve a checkout, including its current status and the payment if one was detected.
GET/v1/checkouts?limit=List checkouts, newest first.
POST/v1/payment_linksCreate a reusable link. Body: title, slug?, amount? (omit to let the customer enter one), currency?, description?, assets, redirect_url?.
GET/v1/payment_linksList payment links.
POST/v1/invoicesCreate an invoice and its checkout. Body: amount, currency?, customer_email?, description?, line_items?, due_date?, assets.
GET/v1/invoices/:idRetrieve an invoice with its checkout and payment status.
GET/v1/paymentsList payments (confirmed, detected and underpaid), newest first.
GET/v1/payments/:idRetrieve a payment with transaction hash, confirmations and the amount received.
GET/v1/assetsSupported assets and which ones your organization has a wallet for.
GET/v1/quotes?asset=XRP&currency=USDCurrent price used for conversion. Informational; checkouts lock their own quote.
POST/v1/webhooks/verifyHelper: send { payload, signature } and get { valid }. Useful in languages without a convenient HMAC.

Objects and statuses

A checkout is one attempt to collect one amount. It moves through these statuses:

createdCheckout exists, no asset chosen yet.
awaiting_paymentAsset chosen, address and exact amount issued, quote locked for 30 minutes.
detectedA matching transaction is on the network but below the required confirmations.
confirmedRequired confirmations reached. Fulfil the order.
underpaidA transaction arrived for less than the amount due.
expiredNo payment before the quote expired. The customer can start again.

A payment is the on-chain transaction that satisfied a checkout: id, checkout_id, asset, amount_crypto (string, minor units), tx_hash, confirmations, from_address?, detected_at, confirmed_at?. Crypto amounts are strings of integer minor units (drops, satoshis, wei, USDC base units) so nothing is lost to floating point.

Webhooks

TapCoin Pay POSTs a JSON body to your endpoint for every event. Deliveries are retried with backoff for 24 hours until your endpoint returns a 2xx. Events can arrive out of order and more than once; use id to deduplicate.

Events

payment.created, payment.detected, payment.confirmed, payment.underpaid, payment.expired, invoice.created, invoice.paid, invoice.expired.

Payload

{ "id": "evt_9Hq2…", "event": "payment.confirmed", "created_at": "2026-09-13T18:41:07Z",
  "data": { "checkout": { … }, "payment": { … }, "invoice": { … } } }

Signature

Every delivery carries a TapCoin-Signature header:

TapCoin-Signature: t=1757789467,v1=5f8c0e…9a1d

To verify: take the raw request body exactly as received, build the string "<t>.<rawBody>", compute HMAC-SHA256 with your endpoint secret, hex-encode it, and compare to v1 with a constant-time comparison. Reject deliveries where t is more than five minutes from your clock.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && expected.length === parts.v1.length
    && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
# PHP
[$t, $v1] = array_map(fn($p) => explode('=', $p, 2)[1], explode(',', $_SERVER['HTTP_TAPCOIN_SIGNATURE']));
$expected = hash_hmac('sha256', $t . '.' . file_get_contents('php://input'), $secret);
$valid = abs(time() - (int)$t) < 300 && hash_equals($expected, $v1);

Use the Send test event button on a webhook in the dashboard to check your endpoint before going live.

Embed script

The embed script adds a Pay with Crypto button to any page without server code. It opens the hosted checkout in a centered modal on desktop and a new tab on mobile.

<script src="https://tapcoinpay.com/js/tapcoin.js" defer></script>

<!-- a pay button created in the dashboard (fixed amount) -->
<button data-tapcoin-button="btn_8f2k1q">Pay with Crypto</button>

<!-- or open a checkout you created server-side -->
<button data-tapcoin-checkout="co_3kD9pQx7">Pay $250.00 in crypto</button>

You can also open a checkout from JavaScript:

window.TapCoin.open({ checkoutId: "co_3kD9pQx7" });   // or { buttonId: "btn_8f2k1q" }

When the payment is confirmed the modal shows a receipt and, if the checkout has a redirect_url, sends the customer there. A button link without JavaScript also works: https://tapcoinpay.com/js/button/btn_8f2k1q creates a checkout and redirects to it.

Integrations

TapCoin Pay plugs into the places you already sell. Every integration uses the same API key and the same signed webhooks; none of them ever sees a wallet key.

IntegrationWhat it doesPlanGet it
WooCommerce / WordPress"Pay with Crypto" at checkout (classic and Blocks), order status automation, shortcodes for pay buttons anywhere. HPOS ready.ProDownload v1.1.0
Embed scriptThree lines of HTML for a pay button on any site: Squarespace, Webflow, Wix, Shopify custom pages, plain HTML.StarterEmbed docs
Payment links and QRNo code. A link for email, social bios and invoices; a QR for print and counters.StarterDashboard
REST API and webhooksCreate checkouts, invoices and links from your own stack. Idempotency keys, signed webhooks, test mode.ProAPI reference
TapCoin POSWeb point of sale on any phone or tablet. Multiple locations on Business.ProOpen POS
XamanOne-tap payments at checkout, wallet sign-in, wallet proof and TapCoin Assets signing.AllXaman docs
AgentPay (x402)Put an API behind a price for AI agents. One HTTP 402, paid on XRPL, verified twice.BusinessAgentPay
n8n / Zapier / MakeAny automation tool that can call a REST API and receive a webhook. Use the API key and the payment.confirmed event.ProWebhooks
ShopifyRoadmap. Today: a payment link or embed button on a custom page.-Soon

WordPress and WooCommerce plugin

  1. In WordPress go to Plugins › Add New, search for TapCoin Pay and install it, or upload tapcoin-pay.zip from your dashboard.
  2. Activate the plugin, then open WooCommerce › Settings › Payments › TapCoin Pay.
  3. Paste a live API key and the webhook secret the plugin shows you. The plugin registers its own webhook URL (/wc-api/tapcoin) with TapCoin Pay automatically.
  4. Choose which assets to offer and the button label. Save.
  5. Place a test order. The order moves to On hold when a payment is detected and Processing when it is confirmed. Underpaid and expired checkouts add an order note.

Requires WordPress 6.2 or newer (tested up to 7.1), WooCommerce 8 or newer (tested up to 11.1) and PHP 8.0 or newer. The plugin never stores wallet keys; it only calls the TapCoin Pay API with your key.

Xaman: pay, sign in, prove

Xaman is the most used XRP Ledger wallet. TapCoin Pay uses its payload API so that anything a wallet has to do becomes a request the customer or merchant approves on their phone. Keys never leave the phone; TapCoin Pay only ever sees the signed result on the ledger.

  • Pay with Xaman. On every XRP checkout the pay page shows a Pay with Xaman button. The request carries the exact amount and destination tag, so the two classic failure modes (wrong amount, missing tag) cannot happen. POST /pay/:id/xaman creates it, GET /pay/:id/xaman/:uuid reports its state. The ledger verifier confirms the payment exactly as it would a manual one.
  • Sign in with Xaman. Link a wallet under Settings once; from then on the login page offers Sign in with Xaman. POST /api/auth/xaman creates a SignIn request, GET /api/auth/xaman/:uuid completes it. Requests are single-use and expire in 30 minutes.
  • Prove a receiving wallet. On the Wallets page, Prove with Xaman asks the wallet at that exact address to sign a no-op. A signature from any other account is rejected. Alternative to the test payment.
  • TapCoin Assets. Connect the issuer wallet, issue, authorize, transfer, deliver and claw back: every pending action can be handed to Xaman with one click and completes from the ledger when Xaman reports the transaction id.

Xaman on the merchant's phone must be on the same network as the deployment: mainnet for payments, Testnet for new TapCoin Assets issuers.

HBAR on Hedera Coming soonLive

HBAR is the next coin. The matching is built and tested. We turn it on after a real payment has gone through end to end. Here is how it will work, so you can build against it now.

HBAR is a core coin alongside XRP and RLUSD. Pass "HBAR" in assets when you create a checkout. Everything else on this page works the same.

How a payment is matched

Hedera has no destination tags. The transaction memo does that job.

  • One memo per checkout. Every HBAR checkout gets a unique memo that starts with tcp:, for example tcp:3f9a1c77be02d4e8a5b1. It is derived from the checkout id, carries no personal data and is not the checkout id itself. The pay page shows it as required, with a copy button.
  • Read from the mirror node. TapCoin Pay reads the public Hedera mirror node for transfers to your account id. A transfer counts when it is a CRYPTOTRANSFER with result SUCCESS and its memo is exactly the one on the checkout.
  • 8 decimals. HBAR amounts are in tinybars: 1 HBAR is 100,000,000 tinybars. The checkout quotes the exact HBAR amount for the fiat price and the customer sends exactly that figure. The amount credited is the sum the ledger says landed in your account, never a number the sender claims.
  • Final on first sight. One confirmation. Once the mirror node lists the transaction as successful it cannot revert. Typical time is 3 to 5 seconds. The receipt links to HashScan.
  • HTS tokens are not HBAR. Token transfers, NFT transfers and staking rewards are ignored. Only native HBAR counts.

If your HBAR lands at an exchange

Exchanges deposit HBAR to one shared account and tell deposits apart by memo. Add that memo on the wallet when you set it up. Checkouts then carry the exchange's memo instead of a unique one, and TapCoin Pay tells payments apart by a unique amount: at most 999 tinybars under the quoted figure, a fraction of a cent, always subtracted and never added. Paste the memo exactly as the exchange shows it or the deposit will not be credited.

Pay with HashPack Rolling out

HashPack is the most used Hedera wallet. On an HBAR checkout the pay page shows a Scan with HashPack QR on a computer and a Pay with HashPack button on a phone. The request is sent over WalletConnect with the amount, your account id and the memo already filled in. The customer approves once in HashPack. The mirror node verifier then confirms the payment exactly as it would a manual one, so a HashPack payment and a hand-typed payment end up in the same place: your own Hedera account. TapCoin Pay never holds the funds and never sees a key.

A customer without HashPack can still pay by hand: copy the account id, the memo and the exact amount into any Hedera wallet.

Fees on HBAR

HBAR fees are written to your fee ledger and billed to your card weekly, like every other coin. The fee-in-the-same-tap option is an XRP and RLUSD feature. The Hedera version comes later.

TapCoin Assets (real-world rights)

Business plan. Turn the rights in a signed agreement into an XRP Ledger Multi-Purpose Token (XLS-33) whose XLS-89 metadata carries the SHA-256 of the published rights manifest, which in turn carries the SHA-256 of the agreement. Everything is signed by the issuer's wallet; TapCoin Pay prepares unsigned transactions and confirms them from the ledger by hash. Overview at tapcoinpay.com/assets.html.

Endpoint (session, /api/assets)Purpose
POST /wallet/xaman, GET /wallet/xaman/:cidProve and record the issuer wallet with a Xaman SignIn (or /wallet/connect + /wallet/verify with an AccountSet memo).
POST /, GET /:id, PATCH /:id, POST /:id/statusAsset records: name, type, description, jurisdiction, valuation, unit price, visibility.
POST /:id/documents (multipart)Upload agreement, photos, appraisals. Hashed, versioned, private by default; images may be PUBLIC for the explorer page.
POST /:id/rights, POST /:id/rights/:mid/publishDraft and publish the rights manifest: rights type, transferable, redeemable, restrictions, legal document. Publishing freezes the canonical JSON and its hash.
POST /:id/token, POST /:id/token/issueConfigure supply, scale and flags from the manifest; return the unsigned MPTokenIssuanceCreate.
POST /:id/pending/:rid/complete or POST /:id/pending/:rid/xamanComplete any pending action by transaction hash, or hand it to Xaman.
POST /:id/holders, POST /:id/transfer, POST /:id/orders, POST /:id/redeemHolder opt-in, compliance-checked transfers, paid orders through the normal checkout, redemption by clawback.
GET /api/public/assets/:ref, POST /api/verify, /assets/:ref, /verifyPublic, no session: explorer data, document verification by hash, the explorer page and the verify page.

Rights agreement templates (ten, editable) ship in the repository under docs/rights-templates/ and in the dashboard wizard. New issuers work on XRPL Testnet; mainnet issuance is enabled per organization by the operator.

Supported coins

Each asset is matched to a checkout differently, because each network offers different tools. The customer never sees this; they see one address and one amount.

AssetNetworkMatching strategyRequired confirmationsTypical time
XRPXRP LedgerOne receiving address plus a unique destination tag per checkout. The QR and the payment URI include the tag. Payments without a tag are flagged for manual matching.13 to 5 seconds
HBAR Coming soonHederaOne account id plus a unique memo per checkout, starting with tcp:. Read from the public mirror node; a payment is final once listed. See HBAR on Hedera.13 to 5 seconds
BTC Coming soonBitcoinA fresh address per checkout, derived from your xpub or zpub (public key only). If you added a single address instead, checkouts are matched by a unique amount (a few extra satoshis).1about 10 minutes
ETH Coming soonEthereum mainnetUnique amount per checkout on your single address (a few extra wei), matched against incoming value transfers.6about 1.5 minutes
USDC Coming soonEthereum mainnet (ERC-20)Unique amount per checkout on your single address, matched against Transfer events to your address.6about 1.5 minutes

Quotes are locked for 30 minutes from when the checkout is created, not from when the customer picks an asset. Payments that arrive after expiry are still detected and shown in your dashboard for manual reconciliation; they are not auto-confirmed against the expired checkout.

Errors and limits

Errors use the same envelope: { "ok": false, "error": { "code", "message" } } with an HTTP status of 400 (validation), 401 (bad key), 402 (plan limit reached), 404, 409 (idempotency conflict) or 429 (rate limit). Live keys are limited to 120 requests a minute; list endpoints to 30. Hosted checkout status polling is rate-limited per session on the server, so your customers never need to worry about it.

Questions or something missing? Email dev@tapcoinpay.com or open an issue on GitHub.

MCP server

Connect any AI assistant to TapCoin Pay

TapCoin Pay speaks the Model Context Protocol, so an AI assistant can create payment links, invoices and checkouts, check payments, run XRP Circles and sell to agents for you, in plain conversation. It uses the same API and the same key as everything on this page, so it can never do more than your key can.

Server URL https://tapcoinpay.com/mcp (Streamable HTTP, stateless JSON).

Connect in one click

Claude, ChatGPT and other clients that follow the MCP authorization spec sign in with a button. Add https://tapcoinpay.com/mcp as a custom connector, click Connect, sign in to TapCoin Pay and approve. The connector then acts for your business with the access you approved (read, write). Step by step instructions with plain words are on the connect page.

  1. Claude: Customize, Connectors, Add custom connector. Paste the URL, leave the OAuth client fields empty, Add, Connect.
  2. ChatGPT: Settings, Connectors, Create. Paste the URL, choose OAuth, Create, sign in.
  3. Claude Code: claude mcp add --transport http tapcoinpay https://tapcoinpay.com/mcp, then /mcp to sign in.

Connected apps are listed under Developers in the dashboard with a Disconnect button. Discovery: protected resource metadata and authorization server metadata. PKCE S256 is required, clients register themselves (RFC 7591), access tokens last an hour, refresh tokens rotate, and the resource parameter (RFC 8707) must be the server URL. Without a token the public tools still work; a call to a business tool answers 401 with WWW-Authenticate: Bearer resource_metadata=..., which is what starts the sign-in.

Or send a key

Scripts, servers and apps without MCP sign-in send a key as Authorization: Bearer ...:

  • API key (tcp_live_..., from Developers in your dashboard): business tools for payments, links, invoices, checkouts, Circles and AgentPay listings.
  • Agent key (agk_..., from the Set up an agent wizard): search the AgentPay directory and check a payment against the agent's spending limits.
  • No key: the public AgentPay directory and live prices.

Cursor, Windsurf and other apps with an mcp.json

{
  "mcpServers": {
    "tapcoinpay": {
      "url": "https://tapcoinpay.com/mcp",
      "headers": { "Authorization": "Bearer tcp_live_your_key" }
    }
  }
}

Apps that support MCP sign-in can drop the headers block and use the URL alone.

Tools

ToolWhat it doesNeeds
tapcoinpay_get_accountYour business, plan and receiving walletsSign-in or API key
tapcoinpay_create_payment_link, _list_payment_linksShareable pay pages and QR codesSign-in or API key
tapcoinpay_create_invoice, _get_invoiceInvoices, emailed to the customerSign-in or API key
tapcoinpay_create_checkout, _list_checkouts, _get_checkoutOne-time checkouts and their statusSign-in or API key
tapcoinpay_list_payments, _get_paymentPayments received, with ledger linksSign-in or API key
tapcoinpay_create_circle, _list_circles, _get_circle, _start_circleXRP Circles (starting one cannot be undone, so assistants are told to ask first)Sign-in or API key
tapcoinpay_create_agentpay_listing, _list_agentpay_listingsSell to AI agentsSign-in or API key
tapcoinpay_check_agent_spendMay my agent pay this? Checks its limitsAgent
tapcoinpay_search_agentpay_directory, tapcoinpay_get_priceFind things agents can buy; live coin pricesNone

No tool moves money, holds keys or signs anything. Assistants create payment requests and read status; the person paying always signs in their own wallet. Never give an assistant a wallet secret.

AgentPay

Charge AI agents per API call

The x402 standard on the XRP Ledger. Full guide on the AgentPay page.

Register something to sell, then hand the gate URL to any x402 client. An unpaid request gets HTTP 402 with machine-readable payment requirements; a request carrying a valid PAYMENT-SIGNATURE header gets the resource, once, after TapCoin Pay has confirmed the payment on the ledger itself.

curl -X POST https://tapcoinpay.com/api/v1/agentpay/resources \
  -H "Authorization: Bearer tcp_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Weather API","pay_to":"rYourXRPLAddress","amount_minor":"250000","source_tag":7777,"origin_url":"https://api.yoursite.com/weather"}'

AgentPay runs on XRP Ledger mainnet. The challenge body tells you which network it is on - xrpl:0 is mainnet, xrpl:1 is testnet - so a client never has to guess. See the AgentPay page for the full flow and a mainnet transaction you can verify yourself.

Directory: let agents find a seller

Every listed resource is in a public, searchable directory. No key needed. Each item carries its price, seller and paid url.

GET https://tapcoinpay.com/api/v1/agentpay/catalog?q=permit&category=data
GET https://tapcoinpay.com/api/v1/agentpay/catalog/<seller-slug>

Spending check: ask before you pay

An agent set up in the dashboard has a key (agk_...) and spending limits. Before signing, it asks whether the payment fits. A yes reserves the amount against its limits; a no comes back as 403 with the rule that refused it. The downloadable client does this automatically when AGENTPAY_KEY is set.

curl -X POST https://tapcoinpay.com/api/v1/agentpay/self/check \
  -H "X-Agent-Key: agk_..." -H "Content-Type: application/json" \
  -d '{"amount_minor":"20000","asset":"XRP","pay_to":"r...","resource_url":"https://tapcoinpay.com/api/v1/agentpay/gate/ares_..."}'

{ "allowed": true, "daySpent": "20000", "monthSpent": "20000" }

Creating paid resources is a Business feature. The agent pays the $0.01 service fee per released call: for anonymous agents it is added to the 402 amount and disclosed as tapcoinpay.serviceFeeMinor (USD cents) and serviceFeeLedger (drops); a registered operator sends X-Agent-Key and the fee comes out of prepaid credits instead. No per-plan call quota. Details on the pricing page.