@profullstack/coinpay-onboard
Take crypto on CoinPay, set up in one pass. The
onboarding behind pay.agent and every fleet site launch.
One engine, idempotent (run it twice, it finds what it made):
- payout addresses on the account (optional)
- a business, found by name or made
- the account's payout wallets copied into it
- its webhook pointed where you say, and the secret
- which coins it can be paid in now
At site launch
npx @profullstack/coinpay-onboard site example.agent --webhook https://example.agent/hooks/coinpay
Logs in as the merchant (COINPAY_EMAIL + COINPAY_PASSWORD, else the
profullstack-private-secrets--prod vault), then writes COINPAY_API_URL,
COINPAY_BUSINESS_ID, COINPAY_API_KEY and COINPAY_WEBHOOK_SECRET into the
site's logicsrc vault (profullstack/example-agent--prod). --no-vault prints
them instead.
In an app (Sign in with CoinPay)
import { CoinPay, onboard, verifyWebhook } from '@profullstack/coinpay-onboard'
import { authorizeUrl, exchangeCode, pkce, refresh } from '@profullstack/coinpay-onboard/oauth'
const { verifier, challenge } = pkce()
redirect(authorizeUrl({ clientId, redirectUri, state, challenge }))
const tokens = await exchangeCode({ code, verifier, clientId, redirectUri })
const coinpay = new CoinPay({ token: tokens.accessToken })
const ready = await onboard(coinpay, { businessName: 'pay.agent/alice', webhookUrl: 'https://pay.agent/hooks/coinpay' })
const payment = await coinpay.createPayment({ businessId: ready.business.id, amountUsd: 5, currency: 'usdc_sol' })
verifyWebhook(rawBody, req.headers['x-coinpay-signature'], ready.webhookSecret!)
Hosted checkout (any coin or PayPal)
The buyer picks the method on CoinPay's page; the site never builds a chain picker.
import { CoinPay, readWebhook, verifyWebhook } from '@profullstack/coinpay-onboard'
const coinpay = new CoinPay({ token: process.env.COINPAY_API_KEY })
const session = await coinpay.createCheckoutSession({
amount: 19,
description: 'Pro plan, 1 month',
customerEmail: user.email,
accountId: user.id,
property: 'example.com',
metadata: { plan: 'pro' },
successUrl: 'https://example.com/billing?session={CHECKOUT_SESSION_ID}',
cancelUrl: 'https://example.com/pricing',
})
redirect(session.url)
if (!verifyWebhook(rawBody, req.headers['x-coinpay-signature'], secret)) return 401
const event = readWebhook(JSON.parse(rawBody))
if (event.paid) grant(event.accountId, event.metadata, event.checkoutSessionId)
readWebhook reads the nested data (the top-level id is the event, not the
payment), treats payment.confirmed and payment.forwarded as paid, and a
PayPal capture (payment.confirmed, status completed, method: 'paypal')
the same. coinpay.checkoutSession(id) polls a session; with the business key
it carries the payer fields and payment ids. With an OAuth token, pass
businessId.
CLI and MCP
coinpay-onboard site <name> --webhook URL launch step, keys into the vault
coinpay-onboard pay <business-id> <usd> [coin]
coinpay-onboard status <payment-id>
coinpay-onboard coins
coinpay-onboard mcp claude mcp add coinpay-onboard -- npx @profullstack/coinpay-onboard mcp
MIT