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.
| Method | Path | Description |
|---|---|---|
POST | /v1/checkouts | Create a checkout. Body: amount, currency, description?, assets?, redirect_url?, customer_email?, metadata?. Honors Idempotency-Key. |
GET | /v1/checkouts/:id | Retrieve a checkout, including its current status and the payment if one was detected. |
GET | /v1/checkouts?limit= | List checkouts, newest first. |
POST | /v1/payment_links | Create a reusable link. Body: title, slug?, amount? (omit to let the customer enter one), currency?, description?, assets, redirect_url?. |
GET | /v1/payment_links | List payment links. |
POST | /v1/invoices | Create an invoice and its checkout. Body: amount, currency?, customer_email?, description?, line_items?, due_date?, assets. |
GET | /v1/invoices/:id | Retrieve an invoice with its checkout and payment status. |
GET | /v1/payments | List payments (confirmed, detected and underpaid), newest first. |
GET | /v1/payments/:id | Retrieve a payment with transaction hash, confirmations and the amount received. |
GET | /v1/assets | Supported assets and which ones your organization has a wallet for. |
GET | /v1/quotes?asset=XRP¤cy=USD | Current price used for conversion. Informational; checkouts lock their own quote. |
POST | /v1/webhooks/verify | Helper: 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.
| Integration | What it does | Plan | Get it |
|---|---|---|---|
| WooCommerce / WordPress | "Pay with Crypto" at checkout (classic and Blocks), order status automation, shortcodes for pay buttons anywhere. HPOS ready. | Pro | Download v1.1.0 |
| Embed script | Three lines of HTML for a pay button on any site: Squarespace, Webflow, Wix, Shopify custom pages, plain HTML. | Starter | Embed docs |
| Payment links and QR | No code. A link for email, social bios and invoices; a QR for print and counters. | Starter | Dashboard |
| REST API and webhooks | Create checkouts, invoices and links from your own stack. Idempotency keys, signed webhooks, test mode. | Pro | API reference |
| TapCoin POS | Web point of sale on any phone or tablet. Multiple locations on Business. | Pro | Open POS |
| Xaman | One-tap payments at checkout, wallet sign-in, wallet proof and TapCoin Assets signing. | All | Xaman docs |
| AgentPay (x402) | Put an API behind a price for AI agents. One HTTP 402, paid on XRPL, verified twice. | Business | AgentPay |
| n8n / Zapier / Make | Any automation tool that can call a REST API and receive a webhook. Use the API key and the payment.confirmed event. | Pro | Webhooks |
| Shopify | Roadmap. Today: a payment link or embed button on a custom page. | - | Soon |
WordPress and WooCommerce plugin
- In WordPress go to Plugins › Add New, search for TapCoin Pay and install it, or upload
tapcoin-pay.zipfrom your dashboard. - Activate the plugin, then open WooCommerce › Settings › Payments › TapCoin Pay.
- 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. - Choose which assets to offer and the button label. Save.
- 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/xamancreates it,GET /pay/:id/xaman/:uuidreports 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/xamancreates a SignIn request,GET /api/auth/xaman/:uuidcompletes 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 exampletcp: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
CRYPTOTRANSFERwith resultSUCCESSand 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/:cid | Prove 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/status | Asset 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/publish | Draft 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/issue | Configure supply, scale and flags from the manifest; return the unsigned MPTokenIssuanceCreate. |
POST /:id/pending/:rid/complete or POST /:id/pending/:rid/xaman | Complete any pending action by transaction hash, or hand it to Xaman. |
POST /:id/holders, POST /:id/transfer, POST /:id/orders, POST /:id/redeem | Holder opt-in, compliance-checked transfers, paid orders through the normal checkout, redemption by clawback. |
GET /api/public/assets/:ref, POST /api/verify, /assets/:ref, /verify | Public, 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.
| Asset | Network | Matching strategy | Required confirmations | Typical time |
|---|---|---|---|---|
| XRP | XRP Ledger | One 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. | 1 | 3 to 5 seconds |
| HBAR Coming soon | Hedera | One 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. | 1 | 3 to 5 seconds |
| BTC Coming soon | Bitcoin | A 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). | 1 | about 10 minutes |
| ETH Coming soon | Ethereum mainnet | Unique amount per checkout on your single address (a few extra wei), matched against incoming value transfers. | 6 | about 1.5 minutes |
| USDC Coming soon | Ethereum mainnet (ERC-20) | Unique amount per checkout on your single address, matched against Transfer events to your address. | 6 | about 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.
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.
- Claude: Customize, Connectors, Add custom connector. Paste the URL, leave the OAuth client fields empty, Add, Connect.
- ChatGPT: Settings, Connectors, Create. Paste the URL, choose OAuth, Create, sign in.
- Claude Code:
claude mcp add --transport http tapcoinpay https://tapcoinpay.com/mcp, then/mcpto 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
| Tool | What it does | Needs |
|---|---|---|
tapcoinpay_get_account | Your business, plan and receiving wallets | Sign-in or API key |
tapcoinpay_create_payment_link, _list_payment_links | Shareable pay pages and QR codes | Sign-in or API key |
tapcoinpay_create_invoice, _get_invoice | Invoices, emailed to the customer | Sign-in or API key |
tapcoinpay_create_checkout, _list_checkouts, _get_checkout | One-time checkouts and their status | Sign-in or API key |
tapcoinpay_list_payments, _get_payment | Payments received, with ledger links | Sign-in or API key |
tapcoinpay_create_circle, _list_circles, _get_circle, _start_circle | XRP Circles (starting one cannot be undone, so assistants are told to ask first) | Sign-in or API key |
tapcoinpay_create_agentpay_listing, _list_agentpay_listings | Sell to AI agents | Sign-in or API key |
tapcoinpay_check_agent_spend | May my agent pay this? Checks its limits | Agent |
tapcoinpay_search_agentpay_directory, tapcoinpay_get_price | Find things agents can buy; live coin prices | None |
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.
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.