
Company News
Jerod Santo Joins Socket as Head of Media
Allow myself to introduce... myself.
@profullstack/coinpay
Advanced tools
CoinPay SDK & CLI — Accept cryptocurrency payments (BTC, ETH, SOL, POL, BCH, USDC) with wallet and swap support
CoinPay SDK & CLI — Accept cryptocurrency payments in your Node.js application.
Non-custodial, multi-chain payment processing for Bitcoin, Ethereum, Solana, Polygon, Bitcoin Cash, and USDC (on ETH, POL, SOL).
┌──────────┐ 1. Create payment ┌──────────┐
│ Your │ ───────────────────────> │ CoinPay │
│ Server │ <─────────────────────── │ API │
│ │ Address + QR code │ │
└────┬─────┘ └────┬─────┘
│ │
│ 2. Show address/QR │ 4. Webhook notification
│ to customer │ (payment confirmed)
▼ │
┌──────────┐ 3. Sends crypto ┌────▼─────┐
│ Customer │ ───────────────────────> │Blockchain│
│ │ │ Network │
└──────────┘ └──────────┘
# pnpm (recommended)
pnpm add @profullstack/coinpay
# npm
npm install @profullstack/coinpay
# Global CLI
pnpm add -g @profullstack/coinpay
Requirements: Node.js ≥ 20. Zero runtime dependencies — uses built-in fetch and crypto.
cp_live_)import { CoinPayClient, Blockchain } from '@profullstack/coinpay';
const client = new CoinPayClient({
apiKey: 'cp_live_your_api_key_here',
});
const { payment } = await client.createPayment({
businessId: 'your-business-id',
amount: 99.99,
currency: 'USD',
blockchain: Blockchain.BTC,
description: 'Order #12345',
metadata: { orderId: '12345' },
});
console.log('Send payment to:', payment.payment_address);
console.log('Amount:', payment.crypto_amount, 'BTC');
console.log('QR Code:', payment.qr_code);
import { createWebhookHandler, WebhookEvent } from '@profullstack/coinpay';
app.post('/webhook', createWebhookHandler({
secret: 'your-webhook-secret',
onEvent: async (event) => {
if (event.type === WebhookEvent.PAYMENT_COMPLETED) {
const orderId = event.data.payment.metadata.orderId;
await markOrderAsPaid(orderId);
}
},
}));
| Blockchain | Code | Type |
|---|---|---|
| Bitcoin | BTC | Native |
| Bitcoin Cash | BCH | Native |
| Ethereum | ETH | Native |
| Polygon | POL | Native |
| Solana | SOL | Native |
| USDC (Ethereum) | USDC_ETH | Stablecoin |
| USDC (Polygon) | USDC_POL | Stablecoin |
| USDC (Solana) | USDC_SOL | Stablecoin |
Use the Blockchain constant to avoid typos:
import { Blockchain } from '@profullstack/coinpay';
Blockchain.BTC // 'BTC'
Blockchain.ETH // 'ETH'
Blockchain.USDC_POL // 'USDC_POL'
The main class for all API operations.
import { CoinPayClient } from '@profullstack/coinpay';
const client = new CoinPayClient({
apiKey: 'cp_live_xxxxx', // Required
baseUrl: 'https://coinpayportal.com/api', // Optional (default)
timeout: 30000, // Optional: ms (default: 30s)
});
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Required. Your CoinPay API key |
baseUrl | string | https://coinpayportal.com/api | API base URL |
timeout | number | 30000 | Request timeout in milliseconds |
Throws Error if apiKey is missing or empty.
client.createPayment(params)Create a new payment request. Generates a unique blockchain address for the customer to pay.
const { payment, usage } = await client.createPayment({
businessId: 'biz_123', // Required — from your dashboard
amount: 100.00, // Required — fiat amount
currency: 'USD', // Optional — fiat currency (default: 'USD')
blockchain: 'ETH', // Required — see Supported Blockchains
description: 'Order #123', // Optional — shown to customer
metadata: { // Optional — your custom data
orderId: '123',
customerEmail: 'a@b.com',
},
});
Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
businessId | string | ✅ | Business ID from your dashboard |
amount | number | ✅ | Amount in fiat currency |
currency | string | — | Fiat currency code (default: 'USD'). Supports: USD, EUR, GBP, CAD, AUD |
blockchain | string | ✅ | Blockchain code (e.g., 'BTC', 'ETH', 'USDC_POL') |
description | string | — | Payment description visible to the customer |
metadata | object | — | Arbitrary key-value data attached to the payment |
Returns:
{
success: true,
payment: {
id: 'pay_abc123',
business_id: 'biz_123',
amount: 100,
currency: 'USD',
blockchain: 'ETH',
crypto_amount: '0.0456',
payment_address: '0x1234...5678',
qr_code: 'data:image/png;base64,...',
status: 'pending',
expires_at: '2024-01-01T01:00:00.000Z',
created_at: '2024-01-01T00:00:00.000Z',
metadata: { orderId: '123' }
},
usage: {
current: 45,
limit: 100,
remaining: 55
}
}
client.getPayment(paymentId)Retrieve a payment by its ID.
const { payment } = await client.getPayment('pay_abc123');
console.log(payment.status); // 'pending', 'confirmed', etc.
console.log(payment.crypto_amount); // '0.0456'
console.log(payment.tx_hash); // '0xabc...def' (once detected)
client.listPayments(params)List payments for a business with optional filtering and pagination.
const { payments } = await client.listPayments({
businessId: 'biz_123', // Required
status: 'completed', // Optional — filter by status
limit: 20, // Optional — results per page (default: 20)
offset: 0, // Optional — pagination offset (default: 0)
});
client.waitForPayment(paymentId, options?)Polls getPayment() until the payment reaches a terminal status. Useful for simple integrations that don't use webhooks.
const { payment } = await client.waitForPayment('pay_abc123', {
interval: 5000, // Poll every 5s (default)
timeout: 600000, // Give up after 10 min (default: 1 hour)
targetStatuses: ['confirmed', 'forwarded', 'expired', 'failed'],
onStatusChange: (status, payment) => {
console.log(`Status → ${status}`);
},
});
if (payment.status === 'confirmed' || payment.status === 'forwarded') {
console.log('Payment successful!');
}
| Option | Type | Default | Description |
|---|---|---|---|
interval | number | 5000 | Polling interval in ms |
timeout | number | 3600000 | Max wait time in ms |
targetStatuses | string[] | ['confirmed','forwarded','expired','failed'] | Statuses that stop polling |
onStatusChange | function | — | Callback (status, payment) => void |
⚠️ For production, use webhooks instead of polling.
| Status | Description |
|---|---|
pending | Waiting for customer to send payment |
detected | Payment detected on blockchain, awaiting confirmations |
confirmed | Payment confirmed — safe to fulfill the order |
forwarding | Forwarding funds to your wallet |
forwarded | Funds successfully sent to your wallet |
expired | Payment request expired (customer didn't pay in time) |
failed | Payment failed |
client.getPaymentQRUrl(paymentId)Returns the URL to the QR code image. Synchronous — no network request.
const url = client.getPaymentQRUrl('pay_abc123');
// "https://coinpayportal.com/api/payments/pay_abc123/qr"
// Use in HTML:
// <img src={url} alt="Payment QR Code" />
client.getPaymentQR(paymentId)Fetches the QR code as binary PNG data.
import fs from 'fs';
const imageData = await client.getPaymentQR('pay_abc123');
fs.writeFileSync('payment-qr.png', Buffer.from(imageData));
client.getExchangeRate(crypto, fiat?)Get the exchange rate for a single cryptocurrency.
const rate = await client.getExchangeRate('BTC', 'USD');
client.getExchangeRates(cryptos, fiat?)Get rates for multiple cryptocurrencies in one request.
const rates = await client.getExchangeRates(['BTC', 'ETH', 'SOL'], 'USD');
client.createBusiness(params)const result = await client.createBusiness({
name: 'My Store',
webhookUrl: 'https://mystore.com/webhook',
walletAddresses: {
BTC: 'bc1q...',
ETH: '0x...',
SOL: '...',
},
});
client.getBusiness(businessId)const result = await client.getBusiness('biz_123');
client.listBusinesses()const result = await client.listBusinesses();
client.updateBusiness(businessId, params)const result = await client.updateBusiness('biz_123', {
name: 'Updated Store Name',
webhookUrl: 'https://mystore.com/webhook/v2',
});
client.getWebhookLogs(businessId, limit?)Retrieve recent webhook delivery logs.
const logs = await client.getWebhookLogs('biz_123', 50);
client.testWebhook(businessId, eventType?)Send a test webhook event to your configured endpoint.
await client.testWebhook('biz_123', 'payment.completed');
Convenience functions that auto-create a client. Best for one-off operations.
import { createPayment, getPayment, listPayments } from '@profullstack/coinpay';
// Create payment without instantiating a client
const result = await createPayment({
apiKey: 'cp_live_xxxxx',
businessId: 'biz_123',
amount: 50,
blockchain: 'BTC',
});
// Or pass an existing client
const result2 = await createPayment({
client: existingClient,
businessId: 'biz_123',
amount: 50,
blockchain: 'BTC',
});
// Get payment
const payment = await getPayment({
apiKey: 'cp_live_xxxxx',
paymentId: 'pay_abc123',
});
// List payments
const list = await listPayments({
apiKey: 'cp_live_xxxxx',
businessId: 'biz_123',
status: 'completed',
limit: 10,
});
import {
Blockchain,
PaymentStatus,
FiatCurrency,
WebhookEvent,
} from '@profullstack/coinpay';
Blockchain| Key | Value | Description |
|---|---|---|
BTC | 'BTC' | Bitcoin |
BCH | 'BCH' | Bitcoin Cash |
ETH | 'ETH' | Ethereum |
POL | 'POL' | Polygon |
SOL | 'SOL' | Solana |
USDC_ETH | 'USDC_ETH' | USDC on Ethereum |
USDC_POL | 'USDC_POL' | USDC on Polygon |
USDC_SOL | 'USDC_SOL' | USDC on Solana |
Cryptocurrencyis exported as a deprecated alias forBlockchain.
PaymentStatus| Key | Value |
|---|---|
PENDING | 'pending' |
CONFIRMING | 'confirming' |
COMPLETED | 'completed' |
EXPIRED | 'expired' |
FAILED | 'failed' |
REFUNDED | 'refunded' |
FiatCurrency| Key | Value |
|---|---|
USD | 'USD' |
EUR | 'EUR' |
GBP | 'GBP' |
CAD | 'CAD' |
AUD | 'AUD' |
WebhookEvent| Key | Value |
|---|---|
PAYMENT_CREATED | 'payment.created' |
PAYMENT_PENDING | 'payment.pending' |
PAYMENT_CONFIRMING | 'payment.confirming' |
PAYMENT_COMPLETED | 'payment.completed' |
PAYMENT_EXPIRED | 'payment.expired' |
PAYMENT_FAILED | 'payment.failed' |
PAYMENT_REFUNDED | 'payment.refunded' |
BUSINESS_CREATED | 'business.created' |
BUSINESS_UPDATED | 'business.updated' |
# Global
pnpm add -g @profullstack/coinpay
# Or use npx
npx @profullstack/coinpay --help
coinpay config set-key cp_live_xxxxx # Save API key
coinpay config set-url http://localhost:3000/api # Custom URL
coinpay config show # Display config
# Create a payment
coinpay payment create \
--business-id biz_123 \
--amount 100 \
--blockchain BTC \
--description "Order #12345"
# Get payment details
coinpay payment get pay_abc123
# List payments
coinpay payment list --business-id biz_123 --status pending --limit 10
# Get QR code
coinpay payment qr pay_abc123
coinpay business list
coinpay business get biz_123
coinpay business create --name "My Store" --webhook-url https://mysite.com/webhook
coinpay business update biz_123 --name "New Name"
coinpay rates get BTC
coinpay rates list
coinpay webhook logs biz_123
coinpay webhook test biz_123 --event payment.completed
When a payment status changes, CoinPay sends a POST request to your webhook URL:
{
"id": "evt_abc123",
"type": "payment.completed",
"created_at": "2024-01-01T00:15:00.000Z",
"business_id": "biz_123",
"data": {
"payment": {
"id": "pay_abc123",
"status": "confirmed",
"amount": 100.00,
"currency": "USD",
"crypto_amount": "0.0456",
"blockchain": "ETH",
"tx_hash": "0xabc...def",
"metadata": { "orderId": "12345" }
}
}
}
Every webhook includes an X-CoinPay-Signature header in the format t=<timestamp>,v1=<hmac-sha256>. Always verify signatures before processing events.
import express from 'express';
import { createWebhookHandler, WebhookEvent } from '@profullstack/coinpay';
const app = express();
app.post('/webhook',
express.text({ type: 'application/json' }),
createWebhookHandler({
secret: process.env.COINPAY_WEBHOOK_SECRET,
onEvent: async (event) => {
switch (event.type) {
case WebhookEvent.PAYMENT_COMPLETED:
await fulfillOrder(event.data.payment.metadata.orderId);
break;
case WebhookEvent.PAYMENT_EXPIRED:
await cancelOrder(event.data.payment.metadata.orderId);
break;
}
},
onError: (error) => {
console.error('Webhook error:', error);
},
})
);
import { verifyWebhookSignature, parseWebhookPayload } from '@profullstack/coinpay';
const isValid = verifyWebhookSignature({
payload: rawBody, // Raw request body string
signature: req.headers['x-coinpay-signature'], // Signature header
secret: process.env.COINPAY_WEBHOOK_SECRET, // Your secret
tolerance: 300, // Optional: seconds (default: 300)
});
if (isValid) {
const event = parseWebhookPayload(rawBody);
// event.id, event.type, event.data, event.createdAt, event.businessId
}
import { generateWebhookSignature } from '@profullstack/coinpay';
const signature = generateWebhookSignature({
payload: JSON.stringify(testEvent),
secret: 'whsec_test_secret',
timestamp: Math.floor(Date.now() / 1000), // Optional
});
// "t=1705312500,v1=a3f2b1..."
| Event | When |
|---|---|
payment.created | Payment request created |
payment.pending | Awaiting blockchain detection |
payment.confirming | Transaction detected, awaiting confirmations |
payment.completed | Payment confirmed — safe to fulfill |
payment.expired | Customer didn't pay in time |
payment.failed | Payment failed |
payment.refunded | Payment was refunded |
business.created | New business created |
business.updated | Business settings updated |
All API errors include a status code and optional response object:
try {
const payment = await client.createPayment({ ... });
} catch (error) {
console.log(error.message); // Human-readable message
console.log(error.status); // HTTP status code (401, 400, 429, etc.)
console.log(error.response); // Full error response from the API
switch (error.status) {
case 400:
// Invalid request — check parameters
break;
case 401:
// Invalid API key
break;
case 404:
// Resource not found
break;
case 429:
// Rate limit or transaction limit exceeded
console.log('Usage:', error.response?.usage);
break;
}
}
Timeout errors throw a standard Error with message "Request timeout after {ms}ms".
Constructor errors throw if apiKey is missing: "API key is required".
import { CoinPayClient, createWebhookHandler, WebhookEvent } from '@profullstack/coinpay';
const client = new CoinPayClient({ apiKey: process.env.COINPAY_API_KEY });
// Checkout endpoint
app.post('/checkout', async (req, res) => {
const { orderId, amount, blockchain } = req.body;
const { payment } = await client.createPayment({
businessId: process.env.COINPAY_BUSINESS_ID,
amount,
blockchain,
description: `Order #${orderId}`,
metadata: { orderId },
});
await db.orders.update(orderId, {
paymentId: payment.id,
paymentAddress: payment.payment_address,
});
res.json({
paymentAddress: payment.payment_address,
cryptoAmount: payment.crypto_amount,
qrCode: payment.qr_code,
expiresAt: payment.expires_at,
});
});
// Webhook
app.post('/webhook', express.text({ type: 'application/json' }),
createWebhookHandler({
secret: process.env.COINPAY_WEBHOOK_SECRET,
onEvent: async (event) => {
if (event.type === WebhookEvent.PAYMENT_COMPLETED) {
const { orderId } = event.data.payment.metadata;
await db.orders.update(orderId, { status: 'paid' });
await sendConfirmationEmail(orderId);
}
},
})
);
Use USDC for predictable pricing — no volatility:
const { payment } = await client.createPayment({
businessId: BUSINESS_ID,
amount: 9.99,
blockchain: Blockchain.USDC_POL, // USDC on Polygon — low fees
description: 'Monthly subscription',
metadata: { userId: user.id, period: '2024-01' },
});
# Create payment
curl -X POST https://coinpayportal.com/api/payments/create \
-H "Authorization: Bearer cp_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"business_id": "your-business-id",
"amount": 50.00,
"currency": "USD",
"blockchain": "ETH"
}'
# Check payment status
curl https://coinpayportal.com/api/payments/pay_abc123 \
-H "Authorization: Bearer cp_live_your_api_key"
Full TypeScript support via .d.ts declaration files — no build step required.
import {
CoinPayClient,
Blockchain,
PaymentStatus,
WebhookEvent,
} from '@profullstack/coinpay';
import type {
CoinPayClientOptions,
PaymentParams,
Payment,
CreatePaymentResponse,
WaitForPaymentOptions,
VerifyWebhookParams,
ParsedWebhookEvent,
} from '@profullstack/coinpay';
const client = new CoinPayClient({ apiKey: 'cp_live_xxxxx' });
const { payment }: CreatePaymentResponse = await client.createPayment({
businessId: 'biz_123',
amount: 100,
blockchain: Blockchain.ETH,
});
// Import only what you need
import { Blockchain, PaymentStatus } from '@profullstack/coinpay/payments';
import { verifyWebhookSignature, WebhookEvent } from '@profullstack/coinpay/webhooks';
| Variable | Description |
|---|---|
COINPAY_API_KEY | API key (overrides config file in CLI) |
COINPAY_BASE_URL | Custom API URL (for development) |
See the examples/ directory for runnable code:
| Example | Description |
|---|---|
01-quick-start.js | Create a payment and check status |
02-create-payment.js | All blockchain types, metadata, multi-currency |
03-check-payment-status.js | One-time check and waitForPayment polling |
04-list-payments.js | Filtering and pagination |
05-exchange-rates.js | Single and batch rate lookups |
06-webhook-handler.js | Express webhook server |
07-ecommerce-checkout.js | Complete checkout → webhook → fulfillment flow |
08-business-management.js | Create, list, and update businesses |
09-error-handling.js | Auth, validation, rate-limit, and timeout errors |
COINPAY_API_KEY=cp_live_xxx COINPAY_BUSINESS_ID=biz_xxx node examples/01-quick-start.js
# Run tests
pnpm test
# Watch mode
pnpm test:watch
Tests use Vitest with mocked fetch — no API key needed.
MIT © Profullstack, Inc.
FAQs
CoinPay SDK & CLI — Accept cryptocurrency and card payments (BTC, ETH, SOL, POL, BCH, USDC) with wallet, swap and a live finances dashboard
The npm package @profullstack/coinpay receives a total of 872 weekly downloads. As such, @profullstack/coinpay popularity was classified as not popular.
We found that @profullstack/coinpay demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.

Security News
Anthropic found biased reasoning and recklessness drove Claude Mythos 5 to publish malware on PyPI and compromise a security vendor.