coal
coal

Quickstart

Accept your first payment in under 5 minutes. This guide walks you through everything from account setup to receiving a real on-chain payment and integrating the API.


Testnet Setup

Before going live, you can develop and test against Base Sepolia — a free test network where no real funds are used.

1. Get test funds

Use the Base Sepolia faucet to receive test ETH for gas fees on Base Sepolia.

2. Enable testnet mode

In your .env.local file, set:

bash
1NEXT_PUBLIC_CHAIN_ENV=testnet

Restart your dev server after changing this value.

3. Embedded wallets on testnet

If you use Privy embedded wallets, they work automatically on testnet — no extra configuration needed. Privy creates wallets on both Base mainnet and Base Sepolia, and your app will route to the correct chain based on the NEXT_PUBLIC_CHAIN_ENV setting.

Note: Test payments made on Base Sepolia will never appear in your live dashboard. Switch NEXT_PUBLIC_CHAIN_ENV back to mainnet (or remove the variable) when you're ready for production.


Step 1: Create Your Account

Head to usecoal.xyz/signup and register a merchant account. You'll need an email address. After signing up, confirm your email and you'll land in the Coal Console.


Step 2: Set Your Payout Address

Before you can receive funds, you need to tell Coal where to send payouts.

  1. In the Console, go to Settings → Payout Address
  2. Paste your EVM wallet address (any wallet that supports ERC-20 tokens on Base)
  3. Click Save

Important: This is the address that will receive every payment. Coal is non-custodial — funds go directly to your wallet, not to Coal. Set this before creating any live payment links.


Step 3: Create a Product

Products let you define what you're selling — name, price, and optional image.

  1. Go to Console → Products → New Product
  2. Fill in:
    • Name — e.g. Premium Membership
    • Price — e.g. 25.00
    • Description (optional) — shown on the checkout page
    • Image (optional) — uploaded via URL or file upload
  3. Click Create Product

Your product is now available to attach to payment links.


Step 4: Create a Payment Link

Payment links are shareable URLs that open a hosted checkout page.

  1. Go to Console → Payment Links → New
  2. Choose a Slug — this becomes the URL path, e.g. premium-membership
  3. Link it to the product you just created
  4. Optionally set:
    • Redirect URL — where to send the buyer after a successful payment
    • Callback URL — your webhook endpoint for server-to-server notifications
  5. Click Create

Your payment link is now live at usecoal.xyz/pay/premium-membership.


Step 5: Test the Checkout

Open https://usecoal.xyz/pay/premium-membership in your browser (or try it in an incognito window).

You'll see the hosted checkout page showing your product name, price, and merchant info. The flow from the user's perspective:

  1. User visits the payment link
  2. They connect their wallet
  3. They approve the on-chain token transfer
  4. Coal submits the transaction hash for on-chain verification
  5. Once confirmed, the user is redirected to your redirectUrl with ?session_id=<id> appended

Step 6: Integrate via API

For programmatic checkout creation — e.g. in your e-commerce backend — use the merchant API directly. These calls use x-api-key; the dashboard and /api/console/* routes use Privy Bearer tokens and are not the primary public merchant surface.

Get Your API Key

Go to Console → API Keys → Create New Key. Copy the key immediately; it will not be shown again.

API keys use the format coal_live_ followed by a random secret. Pass them in the x-api-key header for merchant API requests:

text
1x-api-key: coal_live_your_key_here

API Flow Overview

Create Checkout
Your AppCoal
POST /api/checkouts

x-api-key header · { amount, productName, redirectUrl }

CoalYour App
{ id, url, status: "pending", amount, currency, expiresAt }
User Pays
User / BrowserSends ERC-20 transfer on Base
Your AppCoal
POST /api/pay/confirm

{ sessionId, txHash }

CoalYour App
{ status: "verifying", sessionId }
Poll for Confirmation
Your AppCoal
GET /api/pay/status/:sessionId
CoalYour App
{ status: "confirmed", txHash, redirectUrl }

Initialize a Checkout Session

POST https://api.usecoal.xyz/api/checkouts

bash
1curl -X POST https://api.usecoal.xyz/api/checkouts \
2 -H "x-api-key: coal_live_your_key_here" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "amount": 25,
6 "productName": "Premium Membership",
7 "redirectUrl": "https://yoursite.com/success"
8 }'

Response:

json
1{
2 "data": {
3 "id": "clv9abc123...",
4 "url": "https://usecoal.xyz/pay/checkout/clv9abc123...",
5 "status": "pending",
6 "amount": 25,
7 "currency": "USDC",
8 "expiresAt": "2026-03-23T14:00:00.000Z"
9 }
10}

For flexible/donation links (no fixed product), just send the amount you want to charge.


Confirm Payment (Submit Transaction Hash)

After your frontend sends the payment on-chain, submit the transaction hash to Coal for verification.

POST https://api.usecoal.xyz/api/pay/confirm

bash
1curl -X POST https://api.usecoal.xyz/api/pay/confirm \
2 -H "Content-Type: application/json" \
3 -d '{
4 "sessionId": "cm9x4k2j00003lb08n5qz7v1r",
5 "txHash": "0x3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b"
6 }'

Response:

json
1{
2 "data": {
3 "status": "verifying",
4 "sessionId": "cm9x4k2j00003lb08n5qz7v1r"
5 }
6}

The verifying status means Coal has accepted the hash and the background verification job will process it within the next minute.


Poll Payment Status

GET https://api.usecoal.xyz/api/pay/status/:sessionId

bash
1curl https://api.usecoal.xyz/api/pay/status/cm9x4k2j00003lb08n5qz7v1r

Response (confirmed):

json
1{
2 "status": "confirmed",
3 "txHash": "0x3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b",
4 "redirectUrl": "https://yoursite.com/success"
5}

Possible status values: pending | verifying | confirmed | failed | expired


Node.js Integration Example

Here is a complete example of initializing a checkout and polling for confirmation in a Node.js backend:

typescript
1const COAL_API = 'https://api.usecoal.xyz/api';
2
3// Step 1: Initialize a checkout session
4async function initCheckout(amount: number) {
5 const res = await fetch(`${COAL_API}/checkouts`, {
6 method: 'POST',
7 headers: {
8 'Content-Type': 'application/json',
9 'x-api-key': process.env.COAL_API_KEY!,
10 },
11 body: JSON.stringify({
12 amount,
13 productName: 'Premium Membership',
14 redirectUrl: 'https://yoursite.com/success',
15 }),
16 });
17
18 if (!res.ok) {
19 const err = await res.json();
20 throw new Error(`Checkout init failed: ${JSON.stringify(err)}`);
21 }
22
23 const { data } = await res.json();
24 return data; // { id, url, status, amount, currency, expiresAt }
25}
26
27// Step 2: Submit tx hash after on-chain transfer
28async function confirmPayment(sessionId: string, txHash: string) {
29 const res = await fetch(`${COAL_API}/pay/confirm`, {
30 method: 'POST',
31 headers: { 'Content-Type': 'application/json' },
32 body: JSON.stringify({ sessionId, txHash }),
33 });
34
35 const { data } = await res.json();
36 return data; // { status: "verifying", sessionId }
37}
38
39// Step 3: Poll for final status
40async function waitForConfirmation(sessionId: string, timeoutMs = 120_000) {
41 const deadline = Date.now() + timeoutMs;
42
43 while (Date.now() < deadline) {
44 const res = await fetch(`${COAL_API}/pay/status/${sessionId}`);
45 const { status, txHash, redirectUrl } = await res.json();
46
47 if (status === 'confirmed') return { status, txHash, redirectUrl };
48 if (status === 'failed' || status === 'expired') {
49 throw new Error(`Payment ${status}`);
50 }
51
52 // Not yet confirmed — wait 5 seconds and retry
53 await new Promise(r => setTimeout(r, 5_000));
54 }
55
56 throw new Error('Confirmation timed out');
57}
58
59// Example usage
60const session = await initCheckout('premium-membership');
61console.log(`Send ${session.amount} ${session.currency} to ${session.merchant.payoutAddress}`);
62
63// ... user sends the transfer and you receive the txHash from your frontend ...
64
65await confirmPayment(session.sessionId, '0xabc123...');
66const result = await waitForConfirmation(session.sessionId);
67console.log('Payment confirmed:', result.txHash);

Next Steps

  • Authentication — understand API keys and securing your requests
  • Payment Flow — deep dive into the full end-to-end flow
  • Webhooks — receive server push notifications on payment confirmation
  • Products API — manage your product catalog programmatically