SDK Mode (withCoalPaywall)
Wrap any handler in your own server with coal-payments. Three lines for Next.js, Express, Hono, or Fastify. Coal stays off the request path — the SDK only calls Coal's backend to settle the payment.
Install
1npm install coal-payments
coal-payments is the successor to coal-react. The React checkout components moved to the coal-payments/react sub-path; everything else is new.
Next.js (App Router)
1// app/api/search/route.ts2import { withCoalPaywall } from 'coal-payments/next';34export const GET = withCoalPaywall(5 { paywallId: 'pw_xxx' },6 async (req) => {7 const q = new URL(req.url).searchParams.get('q');8 const hits = await search(q);9 return Response.json({ hits });10 },11);
The wrapped handler only runs after a successful USDC settle on Base. The response gets x-payment-response: 0x<txHash> stamped automatically.
Express
1import express from 'express';2import { coalPaywallMiddleware } from 'coal-payments/express';34const app = express();56app.get(7 '/api/search',8 coalPaywallMiddleware({ paywallId: 'pw_xxx' }),9 (req, res) => {10 // req.coalSettleTx is set after a successful settle.11 res.json({ hits: search(req.query.q) });12 },13);1415app.listen(3000);
Hono
1import { Hono } from 'hono';2import { coalPaywall } from 'coal-payments/hono';34const app = new Hono();56app.get('/api/search', coalPaywall({ paywallId: 'pw_xxx' }), (c) =>7 c.json({ hits: search(c.req.query('q')) }),8);910export default app;
Works in Bun, Deno, Cloudflare Workers, and Node.
Fastify
1import Fastify from 'fastify';2import { coalPaywallHook } from 'coal-payments/fastify';34const app = Fastify();56app.get(7 '/api/search',8 { preHandler: coalPaywallHook({ paywallId: 'pw_xxx' }) },9 async (req, reply) => {10 return { hits: search(req.query.q) };11 },12);1314app.listen({ port: 3000 });
Generic (Bun, Deno, Workers)
If your runtime exposes the standard Request / Response types but you do not use any of the framework adapters:
1import { withCoalPaywall } from 'coal-payments';23const handler = withCoalPaywall(4 { paywallId: 'pw_xxx' },5 async (req) => new Response(JSON.stringify({ ok: true }), {6 headers: { 'content-type': 'application/json' },7 }),8);910export default { fetch: handler };
Options
1interface WithCoalPaywallOptions {2 paywallId: string;3 apiBaseUrl?: string; // default https://api.usecoal.xyz4 attachPayer?: boolean; // if true, forwards x-coal-payer + x-coal-settle-tx into your handler5 fetch?: typeof fetch; // override for test envs / Workers6}
When attachPayer is true, your handler receives a clone of the original Request with two extra headers:
x-coal-payer— the payer's wallet addressx-coal-settle-tx— the on-chain settlement tx hash
Use these to log per-call attribution or apply per-payer business logic.
How the SDK talks to Coal
The SDK is a thin shell. On every call:
- No
X-Paymentheader on the inbound request → SDK callsGET /api/agent/paywalls/{id}/verifyto fetch the current PaymentRequirements, returns 402 to the agent. X-Paymentheader present → SDK callsPOST /api/agent/paywalls/{id}/settlewith the payload. Coal validates + settles on Base + returns{ ok, txHash, payer }.- On settle success → SDK invokes your handler and forwards its response with
x-payment-responsestamped on.
The paywall ID is your identity — and your payout
The only thing you configure is paywallId: 'pw_xxx'. That's enough because the paywall is already bound to you and your wallet — you created it signed into the console, so Coal stored it under your merchant account with your payout address. On every settle, Coal resolves pw_xxx → your account → your wallet, and pays it (atomically splitting the fee through the CoalFeeRouter if you're enrolled). The caller can't redirect it. See how Coal knows it's your API and pays your wallet.
No Coal API key needed here
The payment SDK needs no coal_live_ API key — the paywall ID is the entire identity. The coal_live_ key (from /console/keys) is a separate credential, only for calling Coal's management REST API (checkouts, programmatic paywall management). Don't confuse it with the COAL_FORWARDER_SECRET used by coalOriginGuard in proxy mode.
Migrating from coal-react
coal-react@0.4.1 still installs and works. The new package adds the middleware sub-paths and renames the React entry point. See migration guide.
