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:
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
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>1011 <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 server15 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;2223 const container = document.getElementById('coal-container');24 container.style.display = 'block';2526 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
container | string | Element | Yes | A CSS selector string (e.g. "#checkout-container") or a DOM element. |
options | CoalMountOptions | Yes | Configuration object. See Options below. |
Returns the CoalCheckout instance (chainable).
CoalCheckout.unmount()
Removes the iframe from the DOM and cleans up all event listeners.
1CoalCheckout.unmount();
CoalCheckout.on(event, callback)
Attach an event listener. You can call .on() before or after .mount().
1CoalCheckout.mount('#coal-widget', { sessionId }).on('success', (data) => {2 console.log('Paid:', data.txHash);3});
| Parameter | Type | Description |
|---|---|---|
event | 'ready' | 'success' | 'error' | 'cancel' | Name of the event to listen for. |
callback | Function | Handler called when event fires. |
CoalCheckout.off(event, callback)
Remove a previously registered listener.
1function handleSuccess(data) { /* ... */ }2CoalCheckout.on('success', handleSuccess);3// Later:4CoalCheckout.off('success', handleSuccess);
Options
CoalMountOptions
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
sessionId | string | Yes | — | The session ID returned by your checkout session endpoint. |
baseUrl | string | No | 'https://usecoal.xyz' | Override the Coal server URL (useful for local testing). |
height | number | string | No | 600 | Height of the iframe in pixels (number) or CSS string. |
theme | 'light' | 'dark' | No | — | Color theme forwarded to the embed page. |
onReady | () => void | No | — | Called when the iframe has loaded and is ready. |
onSuccess | (data: SuccessData) => void | No | — | Called when payment is confirmed. |
onError | (data: ErrorData) => void | No | — | Called when an error occurs. |
onCancel | () => void | No | — | Called when the user dismisses the widget. |
Events
The SDK translates postMessage events from the Coal iframe into named callbacks and .on() listeners.
| Event | Payload | When 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
1const widget = CoalCheckout.mount('#coal-widget', { sessionId });23widget.on('ready', () => {4 console.log('Widget ready');5});67widget.on('success', (data) => {8 console.log('Paid!', data.sessionId, data.txHash);9 widget.unmount();10});1112widget.on('error', (data) => {13 console.error('Error:', data.message);14 widget.unmount();15});1617widget.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:
type | Description |
|---|---|
coal:ready | Widget loaded and ready. |
coal:success | Payment confirmed. Includes sessionId and txHash. |
coal:error | Error. Includes message. |
coal:cancel | User dismissed the widget. |
1window.addEventListener('message', function handleCoalEvent(event) {2 // SECURITY: Only accept messages from the Coal widget origin3 if (event.origin !== 'https://usecoal.xyz') return;45 const { type, ...data } = event.data;67 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:
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:
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.
1// pages/api/checkout.ts (Next.js API route)2import type { NextApiRequest, NextApiResponse } from 'next';34export default async function handler(req: NextApiRequest, res: NextApiResponse) {5 if (req.method !== 'POST') return res.status(405).end();67 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 });1213 if (!response.ok) {14 return res.status(500).json({ error: 'Failed to initialize checkout' });15 }1617 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:
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.
