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 a Hubtel provider with your credentials:

import Voltax from "@noelzappy/voltax";
const hubtel = 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 hubtel.initiatePayment({
amount: 100, // 100 GHS
email: "customer@example.com",
currency: Currency.GHS,
reference: `hub-${Date.now()}`, // Required!
callbackUrl: "https://yoursite.com/webhooks/hubtel", // Required!
returnUrl: "https://yoursite.com/payment/success", // Required!
});
// Redirect customer to complete payment
console.log(payment.authorizationUrl);
// "https://checkout.hubtel.com/xxxxxxxxxx"
const payment = await hubtel.initiatePayment({
amount: 250,
email: "customer@example.com",
currency: Currency.GHS,
reference: `order-${Date.now()}`,
description: "Order #12345 - Premium Package",
callbackUrl: "https://yoursite.com/webhooks/hubtel",
returnUrl: "https://yoursite.com/payment/success",
cancellationUrl: "https://yoursite.com/payment/cancelled",
mobileNumber: "0241234567", // Customer's mobile money number
});

All Hubtel-specific options are top-level fields in the payment DTO:

interface HubtelPaymentDTO {
// Base required fields
amount: number;
email: string;
currency: Currency;
reference: string; // Required for Hubtel
callbackUrl: string; // Required for Hubtel
// Hubtel-specific required
returnUrl: string; // Where to redirect after successful payment
// Optional fields
cancellationUrl?: string; // Where to redirect if customer cancels
mobileNumber?: string; // Customer's mobile money number
description?: string;
metadata?: Record<string, any>;
}
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 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 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 hubtel = Voltax("hubtel", {
clientId: process.env.HUBTEL_CLIENT_ID!,
clientSecret: process.env.HUBTEL_CLIENT_SECRET!,
merchantAccountNumber: process.env.HUBTEL_MERCHANT_ACCOUNT!,
});
// Initiate 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 hubtel.initiatePayment({
amount,
email,
currency: Currency.GHS,
reference,
mobileNumber: phone,
description: `Payment for Order #${orderId}`,
callbackUrl: `${process.env.BASE_URL}/webhooks/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 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 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 hubtel.initiatePayment({
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",
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
returnUrlRequired, must be a valid URL

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

  • Learn about Error Handling for Hubtel errors
  • Explore the API Reference for HubtelAdapter
  • Check Hubtel’s documentation for advanced features like recurring payments