CoinsFlow is a crypto payment gateway creators, streamers and open-source projects can take donations through. A pay-what-you-want invoice has no price: the donor opens it, picks Litecoin, Bitcoin, USDT or another coin we take, and sends what they like. A signed webhook tells you when it is paid, and the money is in your balance to withdraw.
Two ways to take a donation
- No code. In the dashboard, press New invoice, choose Any coin they choose and leave the price empty: the form says Pay what you want. Add a description the donor sees, create it, and send the link under Share with the customer. One invoice is one donation; make another for the next donor.
- A Donate button. Your page has a button that calls
POST /v1/invoiceswithopenAmount: trueand sends the donor to the new invoice'spaymentUrl. Every click gets a fresh invoice, so the page always says pay what you want.
Either way the donor sees a fresh address, a QR code, the coins to pick from and the smallest amount that counts. Pasted into a chat, the link shows a preview that says what it is for.
A $10 donation, in numbers
| Amount | whatever the donor sends: there is no price. The page shows the smallest amount of the coin that counts |
|---|---|
| Say the donor sends | $10 worth of Litecoin |
| Seen | seconds after it is sent: invoice.detected |
| Paid | at 1 confirmation, about 2.5 minutes: invoice.paid, with amountPaid and its dollar value, usdValue |
| Our fee | $0.05 (0.5%), taken in LTC when it is credited |
| Donor’s network fee | usually a fraction of a cent, paid by the donor on top |
| In your balance | about $9.95 worth of LTC, withdrawable at once |
The Donate button
import express from "express";
const app = express();
app.use(express.urlencoded({ extended: false }));
// <form method="post" action="/donate"> with an optional "name" field and a Donate button, on your page.
app.post("/donate", async (req, res) => {
const name = String(req.body.name ?? "").trim().slice(0, 40);
const r = await fetch("https://api.coinsflow.app/v1/invoices", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.COINSFLOW_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
openAmount: true, // pay what you want, in the coin the donor picks
description: "Support the channel",
ttlSeconds: 604800, // a week to pay
metadata: { source: "bio", ...(name ? { name } : {}) },
}),
});
if (!r.ok) return res.status(503).send("Donations are not available right now. Try again in a minute.");
const invoice = await r.json();
res.redirect(303, invoice.paymentUrl);
});
app.listen(3000);An invoice stays open for 3 to 7 days (ttlSeconds) and up to 10,000 may wait at once, so make them when someone presses the button, not when a crawler follows a link. metadata is never shown to the donor and comes back in every webhook. Every field is in the API docs.
The webhook says thank you
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, amountPaid, asset, usdValue, metadata } = event.data;
try {
if (!(await thanked(id))) { // your database: one thank-you per invoice
const who = metadata?.name || "Someone";
const worth = usdValue ? ` (about $${usdValue})` : "";
await showOnStream(`${who} sent ${amountPaid} ${asset}${worth}. Thank you!`); // your overlay, chat bot or page
await markThanked(id);
}
res.sendStatus(200);
} catch {
res.sendStatus(500); // CoinsFlow sends it again later
}
});verified() is the signature check from the docs, over the raw body. Add the endpoint in the dashboard, under Developers, with invoice.paid ticked; it must be an https:// address on the public internet. A pay-what-you-want invoice is paid by any confirmed payment: on Litecoin at the first confirmation, about 2.5 minutes after the donor sends, and with USDT on BNB Smart Chain in about 15 seconds. usdValue is the donation's worth in dollars when it was paid.
Getting the money out
Donations are credited to your CoinsFlow balance, in the coin they came in. Withdraw to your own wallet from the dashboard, with the fee shown before you confirm, or set an automatic withdrawal: the balance is sent to your address when it reaches an amount, or every day or week. On Litecoin and Bitcoin a donation is withdrawable as soon as it is paid.
Test it first
Make the invoice with "test": true, then press the buttons on its payment page, or call POST /v1/invoices/{id}/simulate. You get 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 thanks anyone on stream.