coal
coal

SDK Events Reference

The Coal widget communicates with your page through the browser's window.postMessage API. Both the JavaScript SDK's .on() method and React's onReady / onSuccess / onError / onCancel callbacks are built on top of these events.

This page documents every event type, its payload shape, and how to listen for events directly if you are not using an SDK.


Event Overview

EventWhen it fires
coal:readyWidget iframe is fully loaded and ready for user interaction
coal:successPayment confirmed by Coal's verification service
coal:errorPayment failed, was rejected, or encountered an error
coal:cancelUser dismissed the widget without completing payment
coal:closeTreated the same as coal:cancel

Event Payloads

coal:ready

Fired when the widget has mounted and the user can begin the payment flow.

typescript
1interface CoalReadyEvent {
2 type: 'coal:ready';
3 sessionId: string;
4}

Example payload:

json
1{
2 "type": "coal:ready",
3 "sessionId": "cm9x4k2j00003lb08n5qz7v1r"
4}

coal:success

Fired after Coal confirms the transfer. This is the event you should use to unlock content, fulfill orders, or redirect the user.

typescript
1interface CoalPaymentSuccessEvent {
2 type: 'coal:success';
3 sessionId: string;
4 txHash: string;
5}

Example payload:

json
1{
2 "type": "coal:success",
3 "sessionId": "cm9x4k2j00003lb08n5qz7v1r",
4 "txHash": "0x3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b"
5}

Important: coal:success means Coal has verified the transaction on Base. For critical business logic (order fulfillment, access grants), always corroborate by also listening to the checkout.session.completed webhook from your server. Client-side events can be spoofed; webhooks cannot.


coal:error

Fired when the payment cannot be completed. This includes wallet rejections, insufficient balance, network failures, and blocked addresses.

typescript
1interface CoalPaymentErrorEvent {
2 type: 'coal:error';
3 message: string;
4}

Example payload:

json
1{
2 "type": "coal:error",
3 "message": "Your wallet does not have enough of the configured settlement token to complete this payment."
4}

coal:cancel

Fired when the user closes the widget without completing payment (e.g., clicking an X button or pressing Escape). No payload fields beyond type.

typescript
1interface CoalPaymentCancelEvent {
2 type: 'coal:cancel';
3 sessionId: string;
4}

Example payload:

json
1{
2 "type": "coal:cancel",
3 "sessionId": "cm9x4k2j00003lb08n5qz7v1r"
4}

A cancelled session is not reusable — create a new session if the user wants to try again.


Listening via SDK

JavaScript SDK

javascript
1const instance = CoalCheckout.mount('#coal-widget', { sessionId });
2
3instance.on('ready', (data) => {
4 console.log('Widget ready for session:', data.sessionId);
5});
6
7instance.on('success', (data) => {
8 console.log('Paid!', data.txHash);
9 window.location.href = '/success?session_id=' + data.sessionId;
10});
11
12instance.on('error', (data) => {
13 console.error(data.message);
14 showErrorToast(data.message);
15 instance.unmount();
16});
17
18instance.on('cancel', () => {
19 instance.unmount();
20 document.getElementById('coal-widget').style.display = 'none';
21});

React SDK

tsx
1import { CoalWidget } from '@coal/react';
2
3<CoalWidget
4 sessionId={sessionId}
5 onSuccess={(data) => {
6 // data: { sessionId, txHash }
7 router.push(`/success?session_id=${data.sessionId}`);
8 }}
9 onError={(data) => {
10 setError(data.message);
11 }}
12 onCancel={() => {
13 setShowWidget(false);
14 }}
15/>

Listening Directly via window.addEventListener

If you are not using a Coal SDK (e.g., you are embedding the iframe manually), you can listen to raw postMessage events. Always validate the origin before processing.

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:', data.sessionId);
10 break;
11
12 case 'coal:success':
13 console.log('Payment confirmed:', data.txHash);
14 // Fulfill order, redirect, etc.
15 window.removeEventListener('message', handleCoalEvent);
16 break;
17
18 case 'coal:error':
19 console.error('Payment error:', data.message);
20 window.removeEventListener('message', handleCoalEvent);
21 break;
22
23 case 'coal:cancel':
24 case 'coal:close':
25 console.log('User cancelled session:', data.sessionId);
26 window.removeEventListener('message', handleCoalEvent);
27 break;
28 }
29});

Always check event.origin === 'https://usecoal.xyz' before processing. Failing to validate the origin would allow any page to send fake payment success messages to your handler.


TypeScript: Full Union Type

typescript
1export type CoalEvent =
2 | { type: 'coal:ready'; sessionId: string }
3 | { type: 'coal:success'; sessionId: string; txHash: string }
4 | { type: 'coal:error'; message: string }
5 | { type: 'coal:cancel'; sessionId: string };
6
7window.addEventListener('message', (event: MessageEvent) => {
8 if (event.origin !== 'https://usecoal.xyz') return;
9 const coalEvent = event.data as CoalEvent;
10
11 if (coalEvent.type === 'coal:success') {
12 // coalEvent is now narrowed to the success shape
13 handleSuccess(coalEvent.txHash);
14 }
15});