Skip to content
HitchPay API Docs

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.success

Step 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)

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.