coal
coal

JavaScript Widget SDK

Embed the Coal checkout widget into any web page - no framework or build step required. The JavaScript SDK (coal-widget.js) renders a secure iframe that handles the payment flow once your server has created a session. Your page communicates with it via postMessage events.


Installation

CDN (recommended)

Add this script tag anywhere in your HTML before using CoalCheckout:

html
1<script src="https://usecoal.xyz/coal-widget.js" defer></script>

The script exposes window.CoalCheckout as a global object. No bundler or npm install needed.


Quick Start

html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8" />
5 <title>Checkout</title>
6</head>
7<body>
8 <button id="buy-btn">Buy Now</button>
9 <div id="coal-container" style="display:none; width:100%; max-width:480px;"></div>
10
11 <script src="https://usecoal.xyz/coal-widget.js" defer></script>
12 <script>
13 document.getElementById('buy-btn').addEventListener('click', async () => {
14 // Fetch sessionId from your server
15 const res = await fetch('/api/checkout/init', {
16 method: 'POST',
17 headers: { 'Content-Type': 'application/json' },
18 body: JSON.stringify({ slug: 'premium-membership' }),
19 });
20 const { data } = await res.json();
21 const { sessionId } = data;
22
23 const container = document.getElementById('coal-container');
24 container.style.display = 'block';
25
26 CoalCheckout.mount('#coal-container', {
27 sessionId,
28 height: 600,
29 onReady() {
30 console.log('Widget is ready');
31 },
32 onSuccess(data) {
33 console.log('Payment confirmed!', data.txHash);
34 container.style.display = 'none';
35 window.location.href = '/success?session_id=' + data.sessionId;
36 },
37 onError(data) {
38 console.error('Payment error:', data.message);
39 container.style.display = 'none';
40 CoalCheckout.unmount();
41 },
42 onCancel() {
43 container.style.display = 'none';
44 CoalCheckout.unmount();
45 },
46 });
47 });
48 </script>
49</body>
50</html>

API Reference

CoalCheckout.mount(container, options)

Renders the Coal checkout iframe inside a target element.

ParameterTypeRequiredDescription
containerstring | ElementYesA CSS selector string (e.g. "#checkout-container") or a DOM element.
optionsCoalMountOptionsYesConfiguration object. See Options below.

Returns the CoalCheckout instance (chainable).


CoalCheckout.unmount()

Removes the iframe from the DOM and cleans up all event listeners.

javascript
1CoalCheckout.unmount();

CoalCheckout.on(event, callback)

Attach an event listener. You can call .on() before or after .mount().

javascript
1CoalCheckout.mount('#coal-widget', { sessionId }).on('success', (data) => {
2 console.log('Paid:', data.txHash);
3});
ParameterTypeDescription
event'ready' | 'success' | 'error' | 'cancel'Name of the event to listen for.
callbackFunctionHandler called when event fires.

CoalCheckout.off(event, callback)

Remove a previously registered listener.

javascript
1function handleSuccess(data) { /* ... */ }
2CoalCheckout.on('success', handleSuccess);
3// Later:
4CoalCheckout.off('success', handleSuccess);

Options

CoalMountOptions

OptionTypeRequiredDefaultDescription
sessionIdstringYesThe session ID returned by your checkout session endpoint.
baseUrlstringNo'https://usecoal.xyz'Override the Coal server URL (useful for local testing).
heightnumber | stringNo600Height of the iframe in pixels (number) or CSS string.
theme'light' | 'dark'NoColor theme forwarded to the embed page.
onReady() => voidNoCalled when the iframe has loaded and is ready.
onSuccess(data: SuccessData) => voidNoCalled when payment is confirmed.
onError(data: ErrorData) => voidNoCalled when an error occurs.
onCancel() => voidNoCalled when the user dismisses the widget.

Events

The SDK translates postMessage events from the Coal iframe into named callbacks and .on() listeners.

EventPayloadWhen it fires
'ready'{ sessionId? }Iframe has loaded and the user can begin payment.
'success'{ sessionId, txHash }Payment has been confirmed.
'error'{ message }A session load or payment error occurred.
'cancel'{ sessionId? }User dismissed the widget without paying.

Using .on() for events

javascript
1const widget = CoalCheckout.mount('#coal-widget', { sessionId });
2
3widget.on('ready', () => {
4 console.log('Widget ready');
5});
6
7widget.on('success', (data) => {
8 console.log('Paid!', data.sessionId, data.txHash);
9 widget.unmount();
10});
11
12widget.on('error', (data) => {
13 console.error('Error:', data.message);
14 widget.unmount();
15});
16
17widget.on('cancel', () => {
18 console.log('User cancelled');
19 widget.unmount();
20});

Listening Directly via window.addEventListener

If you prefer not to use the SDK, you can listen for raw postMessage events. The Coal iframe fires events with these type strings:

typeDescription
coal:readyWidget loaded and ready.
coal:successPayment confirmed. Includes sessionId and txHash.
coal:errorError. Includes message.
coal:cancelUser dismissed the widget.
javascript
1window.addEventListener('message', function handleCoalEvent(event) {
2 // SECURITY: Only accept messages from the Coal widget origin
3 if (event.origin !== 'https://usecoal.xyz') return;
4
5 const { type, ...data } = event.data;
6
7 switch (type) {
8 case 'coal:ready':
9 console.log('Widget ready');
10 break;
11 case 'coal:success':
12 console.log('Payment confirmed:', data.txHash);
13 window.removeEventListener('message', handleCoalEvent);
14 break;
15 case 'coal:error':
16 console.error('Payment error:', data.message);
17 window.removeEventListener('message', handleCoalEvent);
18 break;
19 case 'coal:cancel':
20 console.log('User cancelled');
21 window.removeEventListener('message', handleCoalEvent);
22 break;
23 }
24});

Always check event.origin === 'https://usecoal.xyz' before trusting message data.


Content Security Policy

If your site uses a Content-Security-Policy header, allow Coal's iframe origin:

text
1Content-Security-Policy: frame-src https://usecoal.xyz; script-src https://usecoal.xyz;

If you load coal-widget.js via npm/bundler instead of the CDN, the script-src directive is not needed:

text
1Content-Security-Policy: frame-src https://usecoal.xyz;

Server-Side Session Creation

Always create sessions on your server. Keep any merchant API keys server-side; the widget only needs the returned session ID.

typescript
1// pages/api/checkout.ts (Next.js API route)
2import type { NextApiRequest, NextApiResponse } from 'next';
3
4export default async function handler(req: NextApiRequest, res: NextApiResponse) {
5 if (req.method !== 'POST') return res.status(405).end();
6
7 const response = await fetch('https://api.usecoal.xyz/api/checkout/init', {
8 method: 'POST',
9 headers: { 'Content-Type': 'application/json' },
10 body: JSON.stringify({ slug: req.body.slug }),
11 });
12
13 if (!response.ok) {
14 return res.status(500).json({ error: 'Failed to initialize checkout' });
15 }
16
17 const { data } = await response.json();
18 return res.json({ sessionId: data.sessionId, amount: data.amount });
19}

If you create hosted checkout sessions with POST /api/checkouts, you can ask Coal to collect payer details before payment:

typescript
1await fetch('https://api.usecoal.xyz/api/checkouts', {
2 method: 'POST',
3 headers: {
4 'Content-Type': 'application/json',
5 'x-api-key': process.env.COAL_API_KEY!,
6 },
7 body: JSON.stringify({
8 amount: 49.99,
9 productName: 'Pro Plan',
10 payerInfo: {
11 required: true,
12 fields: ['fullName', 'email'],
13 },
14 }),
15});

The same payerInfo object can be passed to CoalCheckout.checkout(...) when using the direct JavaScript helper that creates widget sessions for you.