coal
coal

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

  1. Your server creates a checkout session with POST /api/checkouts and returns the sessionId to the browser.
  2. Your frontend mounts the widget with that sessionId.
  3. The widget renders inside a secure iframe from https://usecoal.xyz.
  4. On completion, the widget fires a callback or postMessage event 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

html
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

bash
1npm install @coal/react
tsx
1import { CoalWidget } from '@coal/react';
2
3export function CheckoutPage({ sessionId }: { sessionId: string }) {
4 return (
5 <div className="checkout-container">
6 <CoalWidget
7 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

PropTypeRequiredDefaultDescription
sessionIdstringYes-The session ID from POST /api/checkouts.
baseUrlstringNohttps://usecoal.xyzHost that serves the embed iframe.
theme'light' | 'dark'No'light'Widget color scheme.
onReady() => voidNo-Called when the iframe posts coal:ready.
onSuccess(data: { sessionId: string; txHash: string | null }) => voidNo-Called when payment is confirmed on-chain.
onError(err: { message: string }) => voidNo-Called on payment failure or session expiry.
onCancel() => voidNo-Called when the buyer dismisses the widget.

postMessage Events

The widget communicates via window.postMessage so you can listen from outside React if needed.

javascript
1window.addEventListener('message', function (event) {
2 if (event.origin !== 'https://usecoal.xyz') return;
3
4 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 typePayload fieldsWhen fired
coal:readysessionIdIframe is loaded and ready.
coal:successsessionId, txHashOn-chain confirmation received.
coal:errormessagePayment failed or session expired.
coal:cancelsessionIdBuyer closed the widget.
coal:closesessionIdAlias 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.

typescript
1// app/actions.ts
2'use server';
3
4export 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 });
20
21 if (!res.ok) throw new Error('Could not create checkout session');
22 const session = await res.json();
23 return session.id as string;
24}
tsx
1// app/checkout/page.tsx
2import { initiateCheckout } from '../actions';
3import { WidgetClient } from './WidgetClient';
4
5export default async function CheckoutPage() {
6 const sessionId = await initiateCheckout();
7 return <WidgetClient sessionId={sessionId} />;
8}
tsx
1// app/checkout/WidgetClient.tsx
2'use client';
3import { CoalWidget } from '@coal/react';
4import { useRouter } from 'next/navigation';
5
6export function WidgetClient({ sessionId }: { sessionId: string }) {
7 const router = useRouter();
8 return (
9 <CoalWidget
10 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 postMessage origins before trusting widget events.