All amounts are in kobo (Nigerian lowest denomination). ₦1,200.00 = 120000 kobo. Never pass naira values directly — divide by 100 to display to the customer.
Once your server returns the sessionToken, open the Surge checkout modal.
import { useCallback } from 'react';// Add this once to your index.html:// <script src="https://consumer.gosurge.xyz/surge.js"></script>export default function BuyNowPayLaterButton({ product }) { const handleSurgeCheckout = useCallback(async () => { // 1. Ask your backend to create a checkout session const res = await fetch('/api/surge-checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: product.priceKobo, // e.g. 120000 = ₦1,200 title: product.name, orderReference: `ORD-${Date.now()}`, customerEmail: product.customerEmail, }), }); const { sessionToken, error } = await res.json(); if (error || !sessionToken) { alert('Could not start checkout. Please try again.'); return; } // 2. Open the Surge widget const surge = window.SurgeConnect.init(); surge.openCheckout({ sessionToken, onSuccess: ({ paymentPlanId }) => { // Plan confirmed — NOT yet charged. Show a pending state. window.location.href = `/orders/pending?ref=${paymentPlanId}`; }, onCancel: () => { console.log('Customer closed the widget.'); }, onError: (err) => { console.error('Surge error:', err.message); }, }); }, [product]); return ( <button onClick={handleSurgeCheckout}> Buy Now, Pay Later with Surge </button> );}
onSuccess fires when the customer confirms the plan, not when their deposit is charged. Always use the payment.success webhook (Step 4) as your trigger to fulfill the order.
In your merchant dashboard go to Settings → Webhooks and enter your endpoint URL. Set a strong random string as your Webhook Secret and save it to your SURGE_WEBHOOK_SECRET env var.
// ⚠️ This function must be declared BEFORE app.use(express.json()) is called,// and the route is registered at the top of the file (see Step 2).// JavaScript function declarations are hoisted, so this works even though// it appears later in the file.const crypto = require('crypto');function handleSurgeWebhook(req, res) { const signature = req.headers['x-surge-signature']; const expected = crypto .createHmac('sha256', process.env.SURGE_WEBHOOK_SECRET) .update(req.body) // req.body is a Buffer when using express.raw() .digest('hex'); // timingSafeEqual throws if buffers differ in length, so check first const sigBuf = Buffer.from(signature ?? ''); const expBuf = Buffer.from(expected); if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(expBuf, sigBuf)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body.toString()); if (event.eventType === 'payment.success') { const { orderReference, resourceId } = event.data; // ✅ Safe to fulfill the order now fulfillOrder(orderReference, resourceId); } res.status(200).send('OK');}
Surge signs outbound webhooks with HMAC-SHA256 using your webhook_secret. The header is X-Surge-Signature. Return 200 OK immediately and process fulfillment asynchronously to avoid retry storms.
See the Webhooks guide for all event types and retry behaviour.