Skip to content

Hubtel Integration

Hubtel is a leading payment and commerce platform in Ghana, offering mobile money, card payments, and more. This guide covers how to integrate Hubtel payments using Voltax.

Before you begin, you’ll need:

  • A Hubtel merchant account (sign up at hubtel.com)
  • Your Hubtel API credentials:
    • Client ID
    • Client Secret
    • Merchant Account Number

Initialize Voltax with your Hubtel credentials:

import Voltax from "@noelzappy/voltax";
const voltax = new Voltax({
hubtel: {
clientId: process.env.HUBTEL_CLIENT_ID!,
clientSecret: process.env.HUBTEL_CLIENT_SECRET!,
merchantAccountNumber: process.env.HUBTEL_MERCHANT_ACCOUNT!,
},
});
import { Currency } from "@noelzappy/voltax";
const payment = await voltax.hubtel.initializePayment({
amount: 100, // 100 GHS
email: "customer@example.com",
currency: Currency.GHS,
reference: `hub-${Date.now()}`, // Required!
callbackUrl: "https://yoursite.com/webhooks/hubtel", // Required!
options: {
hubtel: {
returnUrl: "https://yoursite.com/payment/success", // Required!
},
},
});
// Redirect customer to complete payment
console.log(payment.authorizationUrl);
// "https://checkout.hubtel.com/xxxxxxxxxx"
const payment = await voltax.hubtel.initializePayment({
amount: 250,
email: "customer@example.com",
currency: Currency.GHS,
reference: `order-${Date.now()}`,
mobileNumber: "0241234567", // Customer's mobile money number
description: "Order #12345 - Premium Package",
callbackUrl: "https://yoursite.com/webhooks/hubtel",
options: {
hubtel: {
returnUrl: "https://yoursite.com/payment/success",
cancellationUrl: "https://yoursite.com/payment/cancelled",
},
},
});
interface HubtelOptions {
// URL to redirect after successful payment (Required!)
returnUrl: string;
// URL to redirect if customer cancels (Optional, defaults to returnUrl)
cancellationUrl?: string;
}
URLPurpose
callbackUrlServer-to-server webhook URL for payment notifications
returnUrlWhere to redirect the customer after successful payment
cancellationUrlWhere to redirect if customer cancels (optional)

After the customer completes payment, verify the transaction:

import { PaymentStatus } from "@noelzappy/voltax";
const result = await voltax.hubtel.verifyTransaction("hub-123456");
console.log(result);
// {
// status: 'SUCCESS',
// reference: 'hub-123456',
// externalReference: 'txn_xxxxxxxxxx',
// raw: { ... }
// }
if (result.status === PaymentStatus.SUCCESS) {
// Access detailed transaction data
const { amount, charges, amountAfterCharges } = result.raw.data;
console.log(`Received GHS ${amountAfterCharges} (after GHS ${charges} fees)`);
}

Voltax maps Hubtel statuses to standardized values:

Hubtel StatusVoltax Status
PaidSUCCESS
RefundedFAILED
UnpaidPENDING
OtherPENDING

For a quick status check:

const status = await voltax.hubtel.getPaymentStatus("hub-123456");
if (status === PaymentStatus.SUCCESS) {
console.log("Payment successful!");
}

Here’s a full Express.js integration example:

import express from "express";
import Voltax, { Currency, PaymentStatus } from "@noelzappy/voltax";
import { randomUUID } from "crypto";
const app = express();
app.use(express.json());
const voltax = new Voltax({
hubtel: {
clientId: process.env.HUBTEL_CLIENT_ID!,
clientSecret: process.env.HUBTEL_CLIENT_SECRET!,
merchantAccountNumber: process.env.HUBTEL_MERCHANT_ACCOUNT!,
},
});
// Initialize payment
app.post("/api/payments/hubtel", async (req, res) => {
try {
const { amount, email, phone, orderId } = req.body;
const reference = `order-${orderId}-${randomUUID()}`;
const payment = await voltax.hubtel.initializePayment({
amount,
email,
currency: Currency.GHS,
reference,
mobileNumber: phone,
description: `Payment for Order #${orderId}`,
callbackUrl: `${process.env.BASE_URL}/webhooks/hubtel`,
options: {
hubtel: {
returnUrl: `${process.env.FRONTEND_URL}/payment/success?ref=${reference}`,
cancellationUrl: `${process.env.FRONTEND_URL}/payment/cancelled`,
},
},
});
// Store reference in database
await savePaymentReference(orderId, reference);
res.json({
success: true,
checkoutUrl: payment.authorizationUrl,
reference: payment.reference,
});
} catch (error) {
console.error("Payment initialization failed:", error);
res.status(400).json({
success: false,
error: error.message,
});
}
});
// Webhook endpoint (callbackUrl)
app.post("/webhooks/hubtel", async (req, res) => {
const { ClientReference, Status, TransactionId } = req.body;
console.log("Hubtel webhook received:", {
reference: ClientReference,
status: Status,
transactionId: TransactionId,
});
try {
// Verify the transaction
const result = await voltax.hubtel.verifyTransaction(ClientReference);
if (result.status === PaymentStatus.SUCCESS) {
await fulfillOrder(ClientReference);
console.log(`Order ${ClientReference} fulfilled`);
}
res.status(200).json({ received: true });
} catch (error) {
console.error("Webhook processing failed:", error);
res.status(500).json({ error: "Processing failed" });
}
});
// Return URL handler
app.get("/payment/success", async (req, res) => {
const { ref } = req.query;
try {
// Always verify server-side
const result = await voltax.hubtel.verifyTransaction(ref as string);
if (result.status === PaymentStatus.SUCCESS) {
res.render("success", {
message: "Payment successful!",
reference: ref,
});
} else {
res.render("pending", {
message: "Payment is being processed...",
reference: ref,
});
}
} catch (error) {
res.render("error", {
message: "Could not verify payment status",
});
}
});
// Cancellation handler
app.get("/payment/cancelled", (req, res) => {
res.render("cancelled", {
message: "Payment was cancelled",
});
});
app.listen(3000);

Hubtel excels at mobile money payments in Ghana. Include the customer’s mobile number for seamless mobile money checkout:

const payment = await voltax.hubtel.initializePayment({
amount: 50,
email: "customer@example.com",
currency: Currency.GHS,
reference: `momo-${Date.now()}`,
mobileNumber: "0241234567", // MTN, Vodafone, or AirtelTigo number
callbackUrl: "https://yoursite.com/webhooks/hubtel",
options: {
hubtel: {
returnUrl: "https://yoursite.com/success",
},
},
});
CurrencyCodeCountry
Ghanaian CediGHSGhana

Hubtel has specific validation requirements. Here’s what Voltax validates:

FieldRequirement
amountMust be a positive number
referenceRequired, must be provided
callbackUrlRequired, must be a valid URL
options.hubtel.returnUrlRequired, must be a valid URL

If any of these are missing or invalid, you’ll receive a VoltaxValidationError.

import {
VoltaxValidationError,
VoltaxGatewayError,
VoltaxNetworkError,
} from "@noelzappy/voltax";
try {
const payment = await voltax.hubtel.initializePayment({
amount: 100,
email: "customer@example.com",
currency: Currency.GHS,
reference: "test-123",
callbackUrl: "https://yoursite.com/webhook",
options: {
hubtel: { returnUrl: "https://yoursite.com/success" },
},
});
} catch (error) {
if (error instanceof VoltaxValidationError) {
// Missing required fields or invalid values
console.error("Validation error:", error.message);
console.error("Details:", error.errors);
} else if (error instanceof VoltaxGatewayError) {
// Hubtel API error
console.error("Hubtel error:", error.message);
console.error("Status:", error.statusCode);
} else if (error instanceof VoltaxNetworkError) {
// Network connectivity issue
console.error("Network error:", error.message);
}
}
  • Learn about Error Handling for Hubtel errors
  • Explore the API Reference for HubtelAdapter
  • Check Hubtel’s documentation for advanced features like recurring payments