CoinsFlow is a crypto payment gateway for stores that sell things delivered over the internet: keys, accounts, downloads, game items. Your store makes an invoice, the buyer pays it in Litecoin, USDT, Bitcoin or another coin we take, and a signed webhook tells your store to hand the product over. Small payments are fine: the fee is 0.5% of each one, and nothing else.
How it works
- A buyer checks out. Your store makes an invoice with
POST /v1/invoices: the price in dollars, and the product and the customer inmetadata, which the buyer never sees. - The buyer is sent to the invoice's
paymentUrl. With no coin named, they pick one there: Litecoin, Bitcoin, Ether, BNB, USDT or USDC. The page shows the address, the exact amount, a QR code and a countdown. - When it is paid,
invoice.paidreaches your webhook: on Litecoin at the first confirmation, about 2.5 minutes; with USDT on BNB Smart Chain in about 15 seconds. - Your store delivers, once per invoice: it issues the key, unlocks the download, or adds the item to the buyer's account.
- The money is in your balance. Withdraw it to your own wallet, by hand or automatically.
A $6 licence key, in numbers
| Price | $6.00, in the coin the buyer picks, at the current price |
|---|---|
| Paid | Litecoin: 1 confirmation, about 2.5 minutes. USDT on BNB Smart Chain: about 15 seconds. Bitcoin: about 10 minutes |
| Our fee | $0.03 (0.5%), taken when the payment is credited. A $2 item pays $0.01 |
| Buyer’s network fee | Litecoin: usually a fraction of a cent. BNB Smart Chain: cents or less, paid in BNB. Bitcoin: what the network charges that day |
| In your balance | about $5.97 worth of the coin |
The invoice
POST /v1/invoices
{
"priceAmount": "6.00",
"orderId": "order-8812",
"description": "Editor Pro, 1-year licence",
"metadata": { "sku": "editor-pro-1y", "customer": "c_19f3" }
}Send an Idempotency-Key with it, such as your order id: a retried request gets the same invoice back instead of making a second one. metadata comes back in every webhook. The amount in the coin follows the dollar price once an hour until it is paid, so send the buyer the link rather than a number. Every field is in the API docs.
The webhook delivers, once
import express from "express";
// Your products, by the sku the store put in metadata. Nothing else is ever delivered.
const PRODUCTS = {
"editor-pro-1y": () => issueKey("editor-pro", 365), // your licence server
"pack-starter": () => grantItems("starter"), // your game's inventory
};
const app = express();
app.post("/webhooks/coinsflow", express.raw({ type: "application/json" }), async (req, res) => {
if (!Buffer.isBuffer(req.body) || !verified(req.get("CoinsFlow-Signature"), req.body)) return res.sendStatus(400);
const event = JSON.parse(req.body.toString("utf8"));
if (event.type !== "invoice.paid") return res.sendStatus(200);
if (event.test && !process.env.TESTING) return res.sendStatus(200); // test events only while you test
const { id, orderId, metadata } = event.data;
const deliver = PRODUCTS[metadata?.sku];
if (!deliver) return res.sendStatus(200); // not one of the store's invoices
try {
if (!(await delivered(id))) { // your database: one delivery per invoice id, however often this is told
const item = await deliver();
await handOver(metadata.customer, orderId, item); // an email, a download page, the customer's account
await markDelivered(id, item);
}
res.sendStatus(200);
} catch {
res.sendStatus(500); // CoinsFlow sends it again later
}
});
app.listen(3000);verified() is the signature check from the docs, over the raw body. Deliver by the invoice id, never by the order id alone: the same event can arrive again, and after a chain reorganisation an invoice can become paid a second time. Your endpoint must be an https:// address on the public internet; a store that cannot take requests can ask GET /v1/invoices/{id} instead. For an expensive item, wait for invoice.settled, when the network can no longer take the payment back.
Second payments and late ones
Every address we issue is watched for good. A buyer who pays a second time to a paid invoice, or pays after it expired, is not lost: the money is credited to you, and invoice.payment says so. A payment that is short by more than 0.5% makes the invoice underpaid, and the buyer can top it up to the same address; what comes after the invoice expired is credited with paidLate: true. You decide what to deliver in each case; the refund policy says what buyers are told.
What may be sold here
Sell what is yours to sell: keys, accounts, downloads and game items you own the rights to. Our terms (section 6) say what you may not do with the Service:
- Use the Service for anything illegal where you or your customers are, including fraud, money laundering, sanctions evasion, or the sale of illegal goods or services.
- Try to break, overload or reverse-engineer the Service, or access accounts or data that are not yours.
- Resell access to the API as your own payment service without our written agreement.
We may ask for information about you, your business and your customers to meet our legal obligations. We may suspend an account that breaks these terms or that we reasonably believe is being used for fraud; suspension stops new invoices and API access but does not take your balance. A store for a game server has its own rules as well: see Minecraft server stores.
Test it first
Make the invoice with "test": true, then press the buttons on its payment page, or call POST /v1/invoices/{id}/simulate with paid, underpaid, overpaid or expired. Your store gets the same webhooks, marked "test": true, and no money moves. The code above acts on them only while TESTING is set, so a test never hands a product to a real customer.