Storefronts and checkout widgets cover most sellers. But if you run your own SaaS backend, a game launcher, a Discord bot or the till software in a physical shop, you want crypto payments to plug straight into your own systems. That is what a crypto POS API is for.
This guide walks through a complete crypto POS API integration: creating invoices from your server, reacting to webhooks without fulfilling an order twice, taking crypto at an in-store terminal, and reconciling refunds and payouts. The patterns apply to any provider, and where it helps we show how they map to PapelShip.
When do you need a crypto POS API?
An API is the most flexible way to take crypto payments, and also the most work. Before writing code, check whether a ready-made option already covers what you need.
| Option | Best for | Code needed |
|---|---|---|
| Hosted storefront | Selling products from your own store | None |
| Embedded checkout widget | Keeping buyers on your existing site | One line |
| Payment links | Sales in chat, email or communities | None |
| WordPress or XenForo plugin | Sites already built on those platforms | Plugin setup |
| Web API | Custom backends, apps, bots and tills | Your integration |
Reach for the API when the order lives in your system rather than in a store: a subscription in your SaaS, a purchase inside a desktop app, a bot that sells access, or a point-of-sale application at a counter. If you only need a payment button on an existing site, the checkout widget is faster to ship and easier to maintain.
How a crypto POS API integration works
Every integration has the same shape. Your server creates the payment, the customer pays on a page or screen you show them, and the payment platform tells your server what happened. One rule keeps it safe: the client never decides whether an order is paid.
01Create invoice
Amount, currency, your order ID
02Invoice ID and payment page
Shown to the customer to pay
03Webhook: status changed
Sent as the payment progresses
04200 OK
Acknowledge fast, work in the background
05Fetch invoice
Verify the status before acting
06Status: completed
Fulfil once, keyed on invoice ID
The payment itself happens between the customer's wallet and the blockchain. The platform watches the chain, counts confirmations and moves the invoice through its states, as described in crypto payment processing, from checkout to settlement. Your integration never needs a node or a block explorer.
Step 1: create crypto invoices from your server
When a customer decides to buy, your backend asks the API for an invoice with the amount, your currency and your own order reference. The response gives you an invoice identifier to store against the order and a place for the customer to pay.
// Illustrative: runs on your server, never in the browser or app
const res = await fetch(`${API_BASE}/invoices`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAPELSHIP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: "49.00", // a string, never a float
currency: "USD",
reference: order.id, // your own order ID
}),
});
const invoice = await res.json();
await db.orders.update(order.id, { invoiceId: invoice.id, status: "pending" });
// Then send the customer to the payment page, or render it in your checkout- Keep the API key on the server. Anything shipped in a browser bundle, a mobile app or a desktop client can be extracted. PapelShip shows a new API key only once, when you create it, so put it in your secrets manager straight away and never share it with third parties.
- Send amounts as strings or integer minor units. Floating-point rounding is how a $49.00 order quietly becomes $48.99.
- Store the invoice ID before showing the payment page. If the customer pays and your server restarts, you still know which order the payment belongs to.
- Let the platform convert. Price in your own currency and let the invoice fix the crypto amount and its expiry.
Decide what happens when an invoice expires unpaid. The simplest rule is to let the customer start a fresh invoice for the same order, at the current rate, rather than reviving the old one. Keep the expired invoice ID in the order's history, so a late payment against it can still be traced and reviewed.
Step 2: handle webhooks and payment statuses
Webhooks tell your backend when an invoice changes state. They are the fastest signal you will get, and also the one most often mishandled. Assume any webhook can arrive late, twice or out of order, because over enough payments, one eventually will.
- Respond fast. Record the event, return a success response and do the real work in a background job.
- Be idempotent. Use the invoice ID as the key, so a repeated event can't fulfil an order twice.
- Verify before you deliver. Fetch the invoice from the API and act on what it says, not on the webhook body alone. If the provider signs its webhooks, check the signature too.
- Handle every status. Partial, review and expired states deserve a response in your system, not just completed.
// Illustrative
app.post("/webhooks/payments", async (req, res) => {
await queue.add("payment-event", { invoiceId: req.body.invoiceId });
res.sendStatus(200); // acknowledge quickly; the job does the work
});
queue.process("payment-event", async ({ invoiceId }) => {
const invoice = await api.getInvoice(invoiceId); // the source of truth
if (invoice.status !== "completed") return orders.setState(invoiceId, invoice.status);
// Atomic: only one job can move the order from pending to fulfilling
const claimed = await orders.claim(invoiceId, { from: "pending", to: "fulfilling" });
if (!claimed) return; // duplicate event, already handled
await deliver(invoiceId); // license key, file, role or account access
await orders.markFulfilled(invoiceId, invoice.txHash);
});| Invoice state | Your order should |
|---|---|
| Awaiting payment | Stay pending |
| Detected or confirming | Show "confirming"; don't deliver |
| Partially paid | Ask the customer to send the rest |
| Needs review | Wait for a decision |
| Completed | Deliver exactly once |
| Expired or cancelled | Close, and allow a new attempt |
Webhooks are the fast path, not the only path. Run a small scheduled job that fetches any invoice still pending past its expiry and settles its state from the API. It catches the rare event your endpoint missed during a deploy, and it means a webhook outage delays orders instead of losing them.
Crypto at the counter: in-store terminals and POS software
A crypto POS API isn't only for websites. In a physical shop the same invoice flow runs from the till: the cashier rings up the sale, the POS software asks your backend for an invoice, and a customer-facing screen or tablet shows the QR code. The customer scans it with their wallet, and the screen updates as the payment is detected and confirmed.
- 01
Ring up the sale
The POS software sends the basket total and a till reference to your backend, which creates the invoice. The till never talks to the payment API directly.
- 02
Show the QR code
A customer display shows the amount, the coin, the network and the QR code. Make the network as prominent as the amount; our guide to accepting USDT payments explains why.
- 03
Watch the status
The till polls your backend, or listens for a push from it, until the invoice moves from detected to completed.
- 04
Print or send the receipt
Store the transaction hash with the sale, so a refund or a query months later can be traced in seconds.
Two decisions make in-store crypto feel normal. Steer customers towards networks with fast blocks, so nobody stands at the counter waiting for a slow chain, and decide in advance what staff should do when a payment is detected but not yet confirmed. For small purchases some merchants accept that brief risk; for anything valuable, wait for the confirmation.
Plan for connectivity as well. If the till loses its connection mid-payment, the customer's transfer still reaches the chain. When the till reconnects, it should ask your backend for the invoice's current state rather than assuming the sale failed, and staff should never ask a customer to pay a second time until they have checked.
Card terminals sit alongside this rather than being replaced by it. PapelShip's POS API routing is built to connect existing terminals, custom POS endpoints and local Virtual POS gateways, so card and crypto sales from the counter land in the same workspace as your online orders.
Payouts, refunds and reconciliation
With a custodial processor, "payouts" means withdrawing your balance on the processor's schedule. With direct settlement there is no payout step for crypto at all: PapelShip Crypto POS sends each payment straight to your own wallet, with no intermediary holding the funds. What remains is reconciliation, making sure every coin in the wallet maps to an order and every order to a coin. Store these fields with every crypto order:
- The invoice ID from the platform, next to your own order ID
- Coin and network, for example USDT on Tron
- The crypto amount received and the fiat amount it was quoted at
- The transaction hash, so anyone can verify the payment on a block explorer
- A refund state: not refunded, partly refunded or fully refunded
Crypto refunds are new transfers from your wallet to the customer, but they should still be recorded against the original order, so your revenue reports stay correct. Ask the customer for a refund address on the network they paid on, rather than sending to the address the payment came from, which often belongs to an exchange.
Card payments settle through your connected gateway under its own terms. Reseller revenue is different again: with PapelShip's reseller system, debts can be assigned to resellers automatically and collected through your PapelShip E-Commerce account, with reseller revenue routed to your PapelShip balance. The dashboard tracks every transaction, payout and refund in one view, which is the record your accountant will ask for.
Crypto POS API integration checklist
- API key stored server-side only, in a secrets manager
- Prices and invoices created by your backend, never by the client
- Invoice ID stored against the order before the customer pays
- Webhook endpoint that acknowledges fast and processes in a background job
- Idempotent fulfilment keyed on the invoice ID
- Status confirmed through the API before every delivery
- Every state handled, including partial, review and expired
- Transaction hash and network recorded with each payment
- Refunds recorded against the original order
- A full test run, including a duplicate webhook and an expired invoice
Selling access through a bot? Once an invoice completes, the same flow can grant Discord roles for digital products. On a PapelShip store, roles can be set per product variant without writing that part yourself. And if you're still deciding whether crypto belongs in your checkout at all, start with what a crypto POS system is.
Questions fréquemment posées
Build crypto payments into your product
A clean crypto POS API integration is mostly discipline. Create invoices on the server, treat webhooks as signals, verify before you deliver, and keep every refund attached to its original payment.
Get those four right and crypto becomes just another payment method in your system, and one that settles straight into your own wallet.
Équipe Paiements PapelShip
Paiement, crypto et versements