Embedded Widget
Preview status: the widget embed is functional, but it is still treated as a preview surface in the product docs. Prefer the hosted checkout or the current console flows when you need the most stable integration path.
The Coal widget is an iframe-based checkout you can drop into any web page. It keeps signing and payment handling inside the embedded frame so your app never touches private keys or raw blockchain calls.
How it works
- Your server creates a checkout session with
POST /api/checkoutsand returns thesessionIdto the browser. - Your frontend mounts the widget with that
sessionId. - The widget renders inside a secure iframe from
https://usecoal.xyz. - On completion, the widget fires a callback or
postMessageevent so your page can react.
If the session was created with a payerInfo config, the hosted checkout will show those fields before the buyer can continue.
Sessions are single-use. Never reuse a sessionId across buyers.
Embed Option 1 - JavaScript
1<div id="coal-checkout"></div>2<script src="https://usecoal.xyz/coal-widget.js"></script>3<script>4 CoalCheckout.mount('#coal-checkout', {5 sessionId: 'YOUR_SESSION_ID',6 baseUrl: 'https://usecoal.xyz',7 theme: 'light',8 onReady: function () {9 console.log('Widget ready');10 },11 onSuccess: function (data) {12 console.log('Payment confirmed:', data.txHash);13 window.location.href = '/thank-you';14 },15 onError: function (err) {16 console.error('Payment error:', err.message);17 },18 onCancel: function () {19 console.log('Buyer closed the widget');20 },21 });22</script>
Replace YOUR_SESSION_ID with the sessionId returned from your server after calling POST /api/checkouts.
Embed Option 2 - React
1npm install @coal/react
1import { CoalWidget } from '@coal/react';23export function CheckoutPage({ sessionId }: { sessionId: string }) {4 return (5 <div className="checkout-container">6 <CoalWidget7 sessionId={sessionId}8 theme="light"9 primaryColor="#f97316"10 onSuccess={(data) => {11 console.log('Paid!', data.txHash);12 }}13 onError={(err) => {14 console.error('Failed:', err.message);15 }}16 onCancel={() => console.log('closed')}17 />18 </div>19 );20}
The React package exports <CoalWidget> and useCoalCheckout for app-integrated flows.
Props Reference
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
sessionId | string | Yes | - | The session ID from POST /api/checkouts. |
baseUrl | string | No | https://usecoal.xyz | Host that serves the embed iframe. |
theme | 'light' | 'dark' | No | 'light' | Widget color scheme. |
onReady | () => void | No | - | Called when the iframe posts coal:ready. |
onSuccess | (data: { sessionId: string; txHash: string | null }) => void | No | - | Called when payment is confirmed on-chain. |
onError | (err: { message: string }) => void | No | - | Called on payment failure or session expiry. |
onCancel | () => void | No | - | Called when the buyer dismisses the widget. |
postMessage Events
The widget communicates via window.postMessage so you can listen from outside React if needed.
1window.addEventListener('message', function (event) {2 if (event.origin !== 'https://usecoal.xyz') return;34 switch (event.data.type) {5 case 'coal:ready':6 console.log('Widget ready');7 break;8 case 'coal:success':9 console.log('Success:', event.data.txHash, event.data.sessionId);10 break;11 case 'coal:error':12 console.error('Error:', event.data.message);13 break;14 case 'coal:cancel':15 case 'coal:close':16 console.log('Buyer cancelled');17 break;18 }19});
| Event type | Payload fields | When fired |
|---|---|---|
coal:ready | sessionId | Iframe is loaded and ready. |
coal:success | sessionId, txHash | On-chain confirmation received. |
coal:error | message | Payment failed or session expired. |
coal:cancel | sessionId | Buyer closed the widget. |
coal:close | sessionId | Alias for cancel. |
Always verify event.origin === 'https://usecoal.xyz' before trusting the message.
Full Integration Pattern
Create the session on the server, pass sessionId to the client, render the widget.
1// app/actions.ts2'use server';34export async function initiateCheckout() {5 const res = await fetch(`${process.env.COAL_API_URL}/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: 49.99,13 productName: 'Pro Plan',14 payerInfo: {15 required: true,16 fields: ['fullName', 'email'],17 },18 }),19 });2021 if (!res.ok) throw new Error('Could not create checkout session');22 const session = await res.json();23 return session.id as string;24}
1// app/checkout/page.tsx2import { initiateCheckout } from '../actions';3import { WidgetClient } from './WidgetClient';45export default async function CheckoutPage() {6 const sessionId = await initiateCheckout();7 return <WidgetClient sessionId={sessionId} />;8}
1// app/checkout/WidgetClient.tsx2'use client';3import { CoalWidget } from '@coal/react';4import { useRouter } from 'next/navigation';56export function WidgetClient({ sessionId }: { sessionId: string }) {7 const router = useRouter();8 return (9 <CoalWidget10 sessionId={sessionId}11 onSuccess={() => router.push('/success')}12 onError={(err) => console.error(`Payment failed: ${err.message}`)}13 />14 );15}
Security Notes
- Sessions are single-use.
- Sessions expire after 24 hours.
- Keep your merchant API key server-side when creating sessions.
- Verify
postMessageorigins before trusting widget events.
