Webhooks
Webhooks allow your application to receive real-time notifications about events in your Coal account. Instead of polling our API, Coal pushes data to your server as soon as it happens.
[!IMPORTANT] Webhooks are the most reliable way to handle post-payment logic, such as fulfilling orders or updating user balances.
The Webhook Event
Coal currently supports the checkout.session.completed event, which is fired when a payment is successfully confirmed on the blockchain.
Payload Structure
When an event occurs, Coal sends a POST request to your configured callbackUrl. The body of the request will contain the event object.
1{2 "event": "checkout.session.completed",3 "data": {4 "id": "cs_cl9...",5 "amount": "50.00",6 "currency": "MNEE",7 "status": "confirmed",8 "customer_details": {9 "email": "customer@example.com"10 }11 }12}
Security & Verification
To ensure that webhook requests are genuinely from Coal and haven't been tampered with, every request includes a Coal-Signature header.
The header format is:
t=TIMESTAMP,v1=SIGNATURE
Verifying Signatures
You should compute the HMAC-SHA256 of the payload and compare it to the signature provided in the header.
- Extract the timestamp and signature from the header.
- Prepare the
signed_payloadstring: concatenate the timestamp, a period (.), and the raw JSON body. - Compute the HMAC using your API Secret Key.
- Compare your computed signature with the one in the header.
Example Code (Next.js / Node.js)
Here is a robust example of how to handle verify Coal webhooks in a Next.js App Router API route.
1// app/api/webhook/route.ts2import { NextResponse } from 'next/server';3import crypto from 'crypto';45export async function POST(request: Request) {6 const rawBody = await request.text();7 const signatureHeader = request.headers.get('Coal-Signature');89 if (!signatureHeader) {10 return NextResponse.json({ error: "Missing signature" }, { status: 400 });11 }1213 // 1. Extract pieces14 const [t, v1] = signatureHeader.split(',').map(part => part.split('=')[1]);1516 // 2. Prepare payload17 const signedPayload = `${t}.${rawBody}`;1819 // 3. Compute HMAC20 const hmac = crypto.createHmac('sha256', process.env.COAL_SECRET_KEY!);21 const digest = hmac.update(signedPayload).digest('hex');2223 // 4. Compare (Timing Safe)24 if (!crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(v1))) {25 return NextResponse.json({ error: "Invalid signature" }, { status: 401 });26 }2728 // Process Event29 const event = JSON.parse(rawBody);3031 if (event.event === 'checkout.session.completed') {32 const session = event.data;33 console.log(`💰 Payment confirmed: ${session.amount} ${session.currency}`);34 // TODO: Fulfill order35 }3637 return NextResponse.json({ received: true });38}
[!TIP] Always return a
200OK response quickly to acknowledge receipt. If you have complex processing, consider using a background queue.