Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.gosurge.xyz/llms.txt

Use this file to discover all available pages before exploring further.

Step 1 — Get your credentials

  1. Sign up at merchant.gosurge.xyz/register.
  2. After your account is approved, log in and navigate to Settings → API Keys.
  3. Note your Merchant ID (mer_...) shown on that page.
  4. Your credentials for calling the API are your merchant email and password — you exchange them for a JWT token at login (see Step 2).
Auth note: Using email/password on your server is a temporary measure while static API keys are being built. It means your server must store your login password and handle token refresh. If you’d prefer a static key now, email support@gosurge.xyz for early access to surge_live_sk_... keys.
Never hard-code your email, password, or any tokens in source files. Use environment variables and never commit them to git.

Step 2 — Create a checkout session (backend)

Your server logs in to get a JWT, then calls Surge to create a short-lived checkout session tied to the order. The session token is sent to your frontend to open the widget.
const express = require('express');
const { v4: uuidv4 } = require('uuid');

const app = express();

// ⚠️ Register the webhook route BEFORE app.use(express.json())
// so Express does not consume the raw body before signature verification.
// The full handler is defined below — JavaScript hoists function declarations.
app.post('/webhooks/surge', express.raw({ type: 'application/json' }), handleSurgeWebhook);

// Global JSON middleware for all other routes
app.use(express.json());

// --- Auth helper (with 401 retry) ---
let surgeToken = null;

async function fetchNewToken() {
  const res = await fetch('https://api.gosurge.xyz/api/v1/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: process.env.SURGE_MERCHANT_EMAIL,
      password: process.env.SURGE_MERCHANT_PASSWORD,
    }),
  });
  const { data } = await res.json();
  surgeToken = data.token;
  return surgeToken;
}

async function getSurgeToken() {
  if (surgeToken) return surgeToken;
  return fetchNewToken();
}

// --- Checkout session endpoint ---
// Called by your frontend when the customer clicks "Buy Now, Pay Later"
app.post('/api/surge-checkout', async (req, res) => {
  const { amount, title, orderReference, customerEmail } = req.body;

  try {
    const token = await getSurgeToken();

    const response = await fetch('https://api.gosurge.xyz/api/v1/checkout/sessions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': uuidv4(), // prevents duplicate sessions on retry
      },
      body: JSON.stringify({
        merchant_id: process.env.SURGE_MERCHANT_ID,
        amount,           // in kobo — e.g. 120000 = ₦1,200.00
        currency: 'NGN',
        title,
        order_reference: orderReference,
        customer_email: customerEmail,
      }),
    });

    // Token expired — refresh once and retry
    if (response.status === 401) {
      surgeToken = null;
      const retryToken = await fetchNewToken();
      const retry = await fetch('https://api.gosurge.xyz/api/v1/checkout/sessions', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${retryToken}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': uuidv4(),
        },
        body: JSON.stringify({
          merchant_id: process.env.SURGE_MERCHANT_ID,
          amount, currency: 'NGN', title,
          order_reference: orderReference,
          customer_email: customerEmail,
        }),
      });
      const retryResult = await retry.json();
      return res.json({ sessionToken: retryResult.data.session_token });
    }

    if (!response.ok) {
      const error = await response.json();
      return res.status(response.status).json({ error: error.detail });
    }

    const { data } = await response.json();
    res.json({ sessionToken: data.session_token });

  } catch (err) {
    res.status(500).json({ error: 'Failed to create checkout session' });
  }
});

app.listen(3000);
Environment variables you need:
.env
SURGE_MERCHANT_EMAIL=you@yourstore.com
SURGE_MERCHANT_PASSWORD=your-password
SURGE_MERCHANT_ID=mer_...
SURGE_WEBHOOK_SECRET=whsec_...
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.

Step 3 — Open the widget (frontend)

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.

Step 4 — Listen for webhooks

Surge sends a payment.success event to your configured webhook URL after the deposit is collected and the plan is activated.

Configure your webhook URL

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.

Handle the event

Node.js
// ⚠️ 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.

You’re done

StepWhat you built
Step 1Merchant account + credentials
Step 2Server endpoint that logs in and creates a checkout session
Step 3Frontend button that opens the Surge widget
Step 4Webhook handler to fulfill orders after payment
Next steps: