CoinPayments Integration
CoinPayments — one of the oldest crypto payment processors with support for 2000+ coins. For most projects two scenarios are relevant: receiving payments via IPN (Instant Payment Notification) and creating transactions via API.
Authentication and Basic Request
CoinPayments uses HMAC-SHA512 for request signing:
import crypto from "crypto";
import { URLSearchParams } from "url";
const COINPAYMENTS_API = "https://www.coinpayments.net/api.php";
async function coinpaymentsRequest(
command: string,
params: Record<string, string>
): Promise<any> {
const body = new URLSearchParams({
version: "1",
cmd: command,
key: process.env.CP_PUBLIC_KEY!,
format: "json",
...params,
});
const signature = crypto
.createHmac("sha512", process.env.CP_PRIVATE_KEY!)
.update(body.toString())
.digest("hex");
const response = await fetch(COINPAYMENTS_API, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
HMAC: signature,
},
body: body.toString(),
});
const data = await response.json();
if (data.error !== "ok") throw new Error(data.error);
return data.result;
}
Creating Transaction for Payment Reception
async function createTransaction(
amount: string,
currency1: string, // invoice currency (USD, EUR)
currency2: string, // crypto for payment (BTC, ETH, USDT.ERC20)
orderId: string
) {
return coinpaymentsRequest("create_transaction", {
amount,
currency1,
currency2,
buyer_email: "[email protected]",
item_name: `Order ${orderId}`,
custom: orderId, // will be returned in IPN
ipn_url: `${process.env.BASE_URL}/webhooks/coinpayments`,
});
// Returns: { txn_id, address, amount, confirms_needed, timeout, status_url, qrcode_url }
}
Handling IPN (Instant Payment Notification)
IPN — POST request from CoinPayments to your endpoint when payment status changes. Verification is mandatory:
import express from "express";
const router = express.Router();
router.post("/webhooks/coinpayments", express.urlencoded({ extended: true }), (req, res) => {
// Verify signature
const hmac = crypto
.createHmac("sha512", process.env.CP_IPN_SECRET!)
.update(new URLSearchParams(req.body).toString())
.digest("hex");
if (hmac !== req.headers["hmac"]) {
return res.status(400).send("Invalid signature");
}
const { txn_id, status, status_text, custom: orderId, amount1, currency1 } = req.body;
// status >= 100 or status == 2 — fully confirmed
// status >= 0 — processing
// status < 0 — error/cancelled
if (parseInt(status) >= 100 || parseInt(status) === 2) {
// Credit order orderId
processConfirmedPayment(orderId, txn_id, amount1, currency1);
}
res.send("IPN OK"); // CoinPayments expects this response
});
Important: IPN endpoint must respond with string IPN OK (or any 200 response). If there's no response — CoinPayments will retry. Idempotency of processing is mandatory: save txn_id and check for duplicates.
Payment Statuses
| Status | Meaning |
|---|---|
| -2 | Refund / Dispute |
| -1 | Cancelled / Timeout |
| 0 | Waiting for coins |
| 1 | Received, but low confirmations |
| 2 | Completed (only for some coins) |
| 3 | Queued for nightly payout |
| 100 | Fully confirmed |
Common Integration Issues
Transaction timeout: default 2 hours. User may not succeed. Configured via hour parameter, maximum 24 hours. On timeout, if coins arrive later, still accepted as overpaid — handle separately.
IPN doesn't arrive: CoinPayments requires publicly accessible URL. For development — ngrok or similar. In production ensure firewall doesn't block incoming requests from CoinPayments IP.
Exchange rate differences: amount1 in IPN — sum in original currency (USD), amount2 — in crypto. Don't rely only on crypto amount — rate could have changed.
Integration takes 2–3 days including testing IPN via test transactions and handling edge cases.







