Guides & Recipes
One-Time Dynamic Account Payment
This guide walks you through generating a temporary virtual account for a single payment — ideal for checkout flows, order payments, or invoice settlements.
The dynamic account expires in 15 minutes if no payment is received.
Overview
1. Create Dynamic Account POST /v1/payments/dynamic-account
2. Display Account to Payer → show account_number + account_name
3. Poll Payment Status GET /v1/payments/dynamic-account/{tracking_id}
4. Confirm Payment Webhook: payment.successStep 1 — Create a Dynamic Account
Generate a one-time virtual account tied to the exact payment amount.
const axios = require("axios");
const { data: dynamic } = await axios.post(
"https://sandbox.hitchpay.ng/v1/payments/dynamic-account",
{
reference: "ORDER_REF_001",
amount: 15000,
currency: "NGN",
description: "Payment for Order #1042"
},
{ headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }
);
const { tracking_id, account_number, account_name, expires_in } = dynamic.data;
console.log(`Pay ${dynamic.data.amount} NGN to:`);
console.log(`Account: ${account_number}`);
console.log(`Name: ${account_name}`);
console.log(`Expires: ${expires_in}`);<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/payments/dynamic-account",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
"reference" => "ORDER_REF_001",
"amount" => 15000,
"currency" => "NGN",
"description" => "Payment for Order #1042"
]),
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}", "Content-Type: application/json"]
]);
$dynamic = json_decode(curl_exec($ch), true)["data"];
curl_close($ch);
$trackingId = $dynamic["tracking_id"];
$accountNumber = $dynamic["account_number"];
$accountName = $dynamic["account_name"];
echo "Pay {$dynamic['amount']} NGN to {$accountNumber} ({$accountName})";
echo "Expires in: {$dynamic['expires_in']}";import requests
dynamic = requests.post(
"https://sandbox.hitchpay.ng/v1/payments/dynamic-account",
json={
"reference": "ORDER_REF_001",
"amount": 15000,
"currency": "NGN",
"description": "Payment for Order #1042"
},
headers={"Authorization": f"Bearer {access_token}"}
).json()["data"]
tracking_id = dynamic["tracking_id"]
account_number = dynamic["account_number"]
account_name = dynamic["account_name"]
print(f"Pay {dynamic['amount']} NGN to:")
print(f"Account: {account_number}")
print(f"Name: {account_name}")
print(f"Expires: {dynamic['expires_in']}")Step 2 — Display Account Details to the Payer
Show a payment instruction screen to your customer with a countdown timer. Example UI copy:
Transfer exactly ₦15,000 to:
Bank : HitchPay MFB
Account No. : 6020006173
Account Name : HitchAfricaTech Checkout
⏱ This account expires in 15 minutes.
Do not close this page.Always instruct the payer to transfer the exact amount — dynamic accounts are amount-locked.
Step 3 — Poll Payment Status
Poll the status every few seconds while the customer makes the transfer.
async function waitForPayment(trackingId, accessToken, timeoutMs = 900000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const { data: payment } = await axios.get(
`https://sandbox.hitchpay.ng/v1/payments/dynamic-account/${trackingId}`,
{ headers: { Authorization: `Bearer ${accessToken}` } }
);
const { status } = payment.data;
if (status === "paid") {
console.log("Payment received!");
return payment.data;
} else if (status === "expired" || status === "failed") {
throw new Error(`Payment ${status}`);
}
await new Promise(r => setTimeout(r, 5000)); // poll every 5s
}
throw new Error("Payment polling timed out");
}
const result = await waitForPayment(tracking_id, accessToken);<?php
$timeout = time() + 900; // 15 minutes
while (time() < $timeout) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/payments/dynamic-account/{$trackingId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}"]
]);
$payment = json_decode(curl_exec($ch), true)["data"];
curl_close($ch);
if ($payment["status"] === "paid") {
echo "Payment received!";
break;
} elseif (in_array($payment["status"], ["expired", "failed"])) {
throw new Exception("Payment " . $payment["status"]);
}
sleep(5);
}import time
timeout = time.time() + 900 # 15 minutes
while time.time() < timeout:
payment = requests.get(
f"https://sandbox.hitchpay.ng/v1/payments/dynamic-account/{tracking_id}",
headers={"Authorization": f"Bearer {access_token}"}
).json()["data"]
status = payment["status"]
if status == "paid":
print("Payment received!")
break
elif status in ("expired", "failed"):
raise Exception(f"Payment {status}")
time.sleep(5)Step 4 — Handle the Webhook (Recommended)
Rather than polling alone, also listen for the payment.success webhook for instant confirmation.
const crypto = require("crypto");
app.post("/webhooks/hitchpay", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-hitchpay-signature"];
const computed = "sha256=" + crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
if (signature !== computed) return res.status(401).send("Invalid signature");
const event = JSON.parse(req.body);
if (event.event === "payment.success") {
const { reference, amount, currency } = event.data;
console.log(`Payment confirmed: ${amount} ${currency} for ref ${reference}`);
// fulfil the order
}
res.json({ status: "ok" });
});<?php
$payload = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_HITCHPAY_SIGNATURE"] ?? "";
$computed = "sha256=" . hash_hmac("sha256", $payload, "YOUR_WEBHOOK_SECRET");
if (!hash_equals($computed, $signature)) {
http_response_code(401);
exit("Invalid signature");
}
$event = json_decode($payload, true);
if ($event["event"] === "payment.success") {
$ref = $event["data"]["reference"];
$amount = $event["data"]["amount"];
$currency = $event["data"]["currency"];
error_log("Payment confirmed: {$amount} {$currency} for ref {$ref}");
// fulfil the order
}
echo json_encode(["status" => "ok"]);import hmac, hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhooks/hitchpay", methods=["POST"])
def webhook():
signature = request.headers.get("x-hitchpay-signature", "")
computed = "sha256=" + hmac.new(
b"YOUR_WEBHOOK_SECRET",
request.get_data(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, computed):
return "Invalid signature", 401
event = request.get_json()
if event["event"] == "payment.success":
data = event["data"]
print(f"Payment confirmed: {data['amount']} {data['currency']} — {data['reference']}")
# fulfil the order
return jsonify({"status": "ok"})Payment Status Reference
| Status | Description |
|---|---|
pending |
Account generated, awaiting payment |
paid |
Payment received — fulfil the order |
expired |
No payment received within 15 minutes |
failed |
Payment attempt failed |
Summary
| Step | Action | Endpoint |
|---|---|---|
| 1 | Create dynamic account | POST /v1/payments/dynamic-account |
| 2 | Show account details to payer | — |
| 3 | Poll for payment | GET /v1/payments/dynamic-account/{tracking_id} |
| 4 | Handle webhook | payment.success |
💡 Use both polling and webhooks for the best experience — polling drives your UI countdown, webhooks give you instant server-side confirmation.
Questions? Email [email protected]. Also on GitBook.