What you'll build

A complete Interswitch Webpay Redirect integration for Nigeria and pan-African payments. By the end of this tutorial you will have:

Interswitch offers two integration modes: Redirect (Webpay) and Direct. This tutorial covers the Redirect mode, where customers are sent to Interswitch's hosted payment page to complete the transaction.

Get your API key

  1. Go to developer.interswitchgroup.com and create an account
  2. Create a new application to get your credentials
  3. From the app dashboard, note your Product ID, Pay Item ID, MAC Key, Client ID, and Client Secret

Add these to your .env file:

INTERSWITCH_PRODUCT_ID=your_product_id
INTERSWITCH_PAY_ITEM_ID=your_pay_item_id
INTERSWITCH_MAC_KEY=your_mac_key
INTERSWITCH_CLIENT_ID=your_client_id
INTERSWITCH_CLIENT_SECRET=your_client_secret

# Use sandbox for testing, switch to production when ready
INTERSWITCH_PAY_URL=https://sandbox.interswitchng.com/collections/w/pay
INTERSWITCH_QUERY_URL=https://sandbox.interswitchng.com/collections/api/v1/gettransaction.json

Sandbox vs Production: During development, use sandbox.interswitchng.com. The production URL is webpay.interswitchng.com. Switch only when your integration is fully tested and your account has been approved for live transactions.

Build the payment endpoint

Interswitch Webpay works by posting a signed HTML form to their payment page. Your server generates the form with a SHA-512 hash of the transaction details, and the customer's browser submits it.

The hash is critical — it prevents tampering with the amount or other fields after your server generates the form.

BlocWeave prompt

Create an Interswitch Webpay Redirect integration for an Express app. 1. src/services/interswitch.service.ts — export: - generatePaymentHash({ productId, payItemId, amount, redirectUrl, macKey }): Compute SHA-512 of the concatenated string ${productId}${payItemId}${amount}${redirectUrl}${macKey} — no separators, no spaces. Return the hex digest. - buildPaymentForm({ orderId, amount, currency, customerEmail, customerName, redirectUrl }): Construct the payment parameters object with these fields: - product_id: INTERSWITCH_PRODUCT_ID - pay_item_id: INTERSWITCH_PAY_ITEM_ID - amount: amount in minor units (kobo for NGN — multiply by 100) - currency: currency code as integer (566 for NGN, 936 for GHS, 840 for USD) - site_redirect_url: redirectUrl - txn_ref: orderId - cust_id: customerEmail - cust_name: customerName - hash: call generatePaymentHash with the relevant fields (use the minor-unit amount) Return the parameters object and the payment URL (INTERSWITCH_PAY_URL). 2. src/controllers/payments.controller.ts — export: - initiatePayment(req, res, next): - Validate req.body has amount (positive number), currency ("NGN", "GHS", or "USD"), order_id, customer_email, customer_name - Map currency string to code: NGN → 566, GHS → 936, USD → 840 - Call buildPaymentForm with the order details - Save the order to your database with status "pending", the txn_ref, and the original amount - Return 200 with an HTML page containing an auto-submitting form that POSTs to the Interswitch payment URL 3. src/routes/payments.router.ts — POST /interswitch → initiatePayment. Mount under /payments in index.ts.

The auto-submitting form sends the customer to Interswitch's hosted payment page:

// The HTML form your server returns
const html = `
<!DOCTYPE html>
<html>
<body>
  <form id="payForm" method="POST" action="${payUrl}">
    <input type="hidden" name="product_id" value="${params.product_id}" />
    <input type="hidden" name="pay_item_id" value="${params.pay_item_id}" />
    <input type="hidden" name="amount" value="${params.amount}" />
    <input type="hidden" name="currency" value="${params.currency}" />
    <input type="hidden" name="site_redirect_url" value="${params.site_redirect_url}" />
    <input type="hidden" name="txn_ref" value="${params.txn_ref}" />
    <input type="hidden" name="cust_id" value="${params.cust_id}" />
    <input type="hidden" name="cust_name" value="${params.cust_name}" />
    <input type="hidden" name="hash" value="${params.hash}" />
    <noscript><button type="submit">Click to pay</button></noscript>
  </form>
  <script>document.getElementById("payForm").submit();</script>
</body>
</html>
`;

Hash construction matters. The SHA-512 hash must be computed over the exact concatenation product_id + pay_item_id + amount + site_redirect_url + mac_key with no separators. If any field is wrong or the order changes, Interswitch will reject the transaction.

Handle the webhook / verify payment

After payment, Interswitch redirects the customer back to your site_redirect_url with query parameters. Always verify the transaction server-side — never trust the redirect parameters alone.

BlocWeave prompt

Add Interswitch payment verification and redirect handling. 1. src/services/interswitch.service.ts — add: - verifyTransaction(txnRef, productId, amount): POST to INTERSWITCH_QUERY_URL with query params: productid=productId, transactionreference=txnRef, amount=amount. Set headers: Authorization (Basic base64 of INTERSWITCH_CLIENT_ID:INTERSWITCH_CLIENT_SECRET). Return the response body. 2. src/controllers/payments.controller.ts — add: - interswitchRedirect(req, res, next): - Extract txnRef and payRef from req.query (Interswitch appends these to the redirect URL) - Look up the order in your database by txn_ref - If order not found, redirect to frontend error page - Call verifyTransaction(txnRef, INTERSWITCH_PRODUCT_ID, order.amount_in_minor_units) - Check ResponseCode in the verification response: - "00" means successful — update order status to "paid", save the payRef and response details - Any other code means failed — update order status to "failed" with the ResponseDescription - Redirect to your frontend: /payment/success?order=ORDER_ID or /payment/failed?order=ORDER_ID&reason=DESCRIPTION 3. src/routes/payments.router.ts — add GET /interswitch/redirect → interswitchRedirect.

// Verification response (success)
{
  "ResponseCode": "00",
  "ResponseDescription": "Approved Successful",
  "Amount": 500000,
  "MerchantReference": "ORD-12345",
  "PaymentReference": "FBN|WEB|MX1234|01-01-2026"
}

ResponseCode "00" is the only success. Interswitch uses a variety of response codes for different failure scenarios. Treat anything other than "00" as a failed payment. Common codes include "Z6" (insufficient funds), "51" (declined), and "Z0" (transaction not found).

Test it

  1. Set your environment variables and start the server:
npm run dev
  1. Initiate a payment:
curl -X POST http://localhost:3000/payments/interswitch \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ORD-TEST-001",
    "amount": 5000,
    "currency": "NGN",
    "customer_email": "[email protected]",
    "customer_name": "Test Customer"
  }'
  1. The response is an HTML page — open it in a browser or pipe it to a file and open it. The form auto-submits to the Interswitch sandbox payment page.

  2. On the sandbox payment page, use the test card details provided in your Interswitch developer account.

  3. After completing payment, Interswitch redirects to your site_redirect_url. Your server verifies the transaction and redirects to the appropriate frontend page.

  4. Check your database — the order should show status paid with the Interswitch payment reference.

  5. Test failure cases: use an invalid card or cancel the payment on the Interswitch page to confirm your error handling works.

Project structure after this tutorial

src/
  controllers/
    payments.controller.ts
  routes/
    payments.router.ts
  services/
    interswitch.service.ts