Skip to content
HitchPay API Docs

Guides & Recipes

Send Money Internationally

This guide walks you through the full flow for sending funds cross-border to a beneficiary's bank account or mobile money wallet.


Overview

1. Enroll Customer      POST /v1/customer/enroll
2. Submit KYC           POST /v1/customer/{uuid}/kyc
3. Poll KYC Status      GET  /v1/customer/{uuid}/kyc
4. Get Channels         GET  /v1/crossborder/channels
5. Get Networks         GET  /v1/crossborder/networks
6. Get Exchange Rate    GET  /v1/crossborder/rates
7. Initiate Transfer    POST /v1/crossborder/global-transfer
8. Check Status         GET  /v1/crossborder/global-transfer/{reference}

Step 1 — Enroll the Customer

Register your customer on HitchPay to get their uuid.

const axios = require("axios");

const { data: enrollment } = await axios.post(
  "https://sandbox.hitchpay.ng/v1/customer/enroll",
  {
    customerid: "CUST_001",
    email: "[email protected]",
    phone: "07068363556",
    firstname: "John",
    lastname: "Doe",
    dob: "01-01-1990",
    nationality: "NG",
    image: "https://storage.yourapp.com/profile.jpg",
    address: {
      house_no: 5,
      street: "10 Marina Street",
      city: "Lagos",
      state: "Lagos",
      postal_code: "100001",
      country: "NG"
    }
  },
  { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }
);

const customerUuid = enrollment.data.uuid;
console.log("Customer UUID:", customerUuid);
<?php

$ch = curl_init();
curl_setopt_array($ch, [
  CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/customer/enroll",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode([
    "customerid"  => "CUST_001",
    "email"       => "[email protected]",
    "phone"       => "07068363556",
    "firstname"   => "John",
    "lastname"    => "Doe",
    "dob"         => "01-01-1990",
    "nationality" => "NG",
    "image"       => "https://storage.yourapp.com/profile.jpg",
    "address"     => [
      "house_no" => 5, "street" => "10 Marina Street",
      "city" => "Lagos", "state" => "Lagos",
      "postal_code" => "100001", "country" => "NG"
    ]
  ]),
  CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}", "Content-Type: application/json"]
]);

$enrollment  = json_decode(curl_exec($ch), true);
curl_close($ch);
$customerUuid = $enrollment["data"]["uuid"];
import requests

enrollment = requests.post(
    "https://sandbox.hitchpay.ng/v1/customer/enroll",
    json={
        "customerid": "CUST_001", "email": "[email protected]",
        "phone": "07068363556", "firstname": "John", "lastname": "Doe",
        "dob": "01-01-1990", "nationality": "NG",
        "image": "https://storage.yourapp.com/profile.jpg",
        "address": {
            "house_no": 5, "street": "10 Marina Street",
            "city": "Lagos", "state": "Lagos",
            "postal_code": "100001", "country": "NG"
        }
    },
    headers={"Authorization": f"Bearer {access_token}"}
).json()

customer_uuid = enrollment["data"]["uuid"]

Step 2 — Submit KYC Documents

await axios.post(
  `https://sandbox.hitchpay.ng/v1/customer/${customerUuid}/kyc`,
  {
    type: "passport",
    country: "NG",
    document_front: "https://storage.yourapp.com/kyc/passport-front.jpg"
  },
  { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }
);
<?php

$ch = curl_init();
curl_setopt_array($ch, [
  CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/customer/{$customerUuid}/kyc",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode([
    "type" => "passport", "country" => "NG",
    "document_front" => "https://storage.yourapp.com/kyc/passport-front.jpg"
  ]),
  CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}", "Content-Type: application/json"]
]);
curl_exec($ch);
curl_close($ch);
requests.post(
    f"https://sandbox.hitchpay.ng/v1/customer/{customer_uuid}/kyc",
    json={
        "type": "passport", "country": "NG",
        "document_front": "https://storage.yourapp.com/kyc/passport-front.jpg"
    },
    headers={"Authorization": f"Bearer {access_token}"}
)

Step 3 — Wait for KYC Approval

Poll until overall_kyc_status is approved, or listen for the crossborder.kyc.approved webhook.

let approved = false;

while (!approved) {
  const { data: kyc } = await axios.get(
    `https://sandbox.hitchpay.ng/v1/customer/${customerUuid}/kyc`,
    { headers: { Authorization: `Bearer ${accessToken}` } }
  );

  if (kyc.data.overall_kyc_status === "approved") {
    approved = true;
    console.log("KYC approved — ready to transact");
  } else if (kyc.data.overall_kyc_status === "rejected") {
    throw new Error("KYC rejected: " + kyc.data.submissions[0]?.reason);
  } else {
    await new Promise(r => setTimeout(r, 5000)); // wait 5s and retry
  }
}
<?php

while (true) {
  $ch = curl_init();
  curl_setopt_array($ch, [
    CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/customer/{$customerUuid}/kyc",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}"]
  ]);
  $kyc = json_decode(curl_exec($ch), true);
  curl_close($ch);

  if ($kyc["data"]["overall_kyc_status"] === "approved") {
    echo "KYC approved";
    break;
  } elseif ($kyc["data"]["overall_kyc_status"] === "rejected") {
    throw new Exception("KYC rejected");
  }
  sleep(5);
}
import time

while True:
    kyc = requests.get(
        f"https://sandbox.hitchpay.ng/v1/customer/{customer_uuid}/kyc",
        headers={"Authorization": f"Bearer {access_token}"}
    ).json()

    status = kyc["data"]["overall_kyc_status"]
    if status == "approved":
        print("KYC approved — ready to transact")
        break
    elif status == "rejected":
        raise Exception("KYC rejected")
    time.sleep(5)

Step 4 — Discover Channels & Networks

Fetch the available payment channels and bank networks for the destination country.

const { data: channels } = await axios.get(
  "https://sandbox.hitchpay.ng/v1/crossborder/channels",
  { params: { country_code: "KE", ramptype: "withdrawal" },
    headers: { Authorization: `Bearer ${accessToken}` } }
);

const channelId = channels.data.find(c => c.value === "bank").channelid;

const { data: networks } = await axios.get(
  "https://sandbox.hitchpay.ng/v1/crossborder/networks",
  { params: { country_code: "KE", channel: "bank" },
    headers: { Authorization: `Bearer ${accessToken}` } }
);

const networkId = networks.data[0].network_id; // pick the right bank
<?php

// Get channels
$ch = curl_init();
curl_setopt_array($ch, [
  CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/crossborder/channels?country_code=KE&ramptype=withdrawal",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}"]
]);
$channels = json_decode(curl_exec($ch), true);
curl_close($ch);

$channelId = collect($channels["data"])->firstWhere("value", "bank")["channelid"];

// Get networks
$ch = curl_init();
curl_setopt_array($ch, [
  CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/crossborder/networks?country_code=KE&channel=bank",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}"]
]);
$networks = json_decode(curl_exec($ch), true);
curl_close($ch);

$networkId = $networks["data"][0]["network_id"];
channels = requests.get(
    "https://sandbox.hitchpay.ng/v1/crossborder/channels",
    params={"country_code": "KE", "ramptype": "withdrawal"},
    headers={"Authorization": f"Bearer {access_token}"}
).json()

channel_id = next(c["channelid"] for c in channels["data"] if c["value"] == "bank")

networks = requests.get(
    "https://sandbox.hitchpay.ng/v1/crossborder/networks",
    params={"country_code": "KE", "channel": "bank"},
    headers={"Authorization": f"Bearer {access_token}"}
).json()

network_id = networks["data"][0]["network_id"]

Step 5 — Get a Live Exchange Rate

Always fetch a fresh rate immediately before initiating the transfer.

const { data: rate } = await axios.get(
  "https://sandbox.hitchpay.ng/v1/crossborder/rates",
  { params: { source_currency: "USD", destination_currency: "KES", amount: 100 },
    headers: { Authorization: `Bearer ${accessToken}` } }
);

console.log(`Rate: 1 USD = ${rate.data.exchange_rate} KES`);
console.log(`Recipient gets: ${rate.data.destination_amount} KES`);
<?php

$ch = curl_init();
curl_setopt_array($ch, [
  CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/crossborder/rates?source_currency=USD&destination_currency=KES&amount=100",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}"]
]);
$rate = json_decode(curl_exec($ch), true);
curl_close($ch);

echo "Recipient gets: " . $rate["data"]["destination_amount"] . " KES";
rate = requests.get(
    "https://sandbox.hitchpay.ng/v1/crossborder/rates",
    params={"source_currency": "USD", "destination_currency": "KES", "amount": 100},
    headers={"Authorization": f"Bearer {access_token}"}
).json()

print(f"Recipient gets: {rate['data']['destination_amount']} KES")

Step 6 — Initiate the Transfer

const { data: transfer } = await axios.post(
  "https://sandbox.hitchpay.ng/v1/crossborder/global-transfer",
  {
    customer_uuid: customerUuid,
    amount: 100,
    source_currency: "USD",
    destination_currency: "KES",
    reference: "REF_TRANSFER_001",
    reason: "School fees",
    beneficiary: {
      account_name: "Jane Doe",
      account_number: "0123456789",
      network_id: networkId,
      channel_id: channelId,
      account_type: "bank",
      country_code: "KE"
    }
  },
  { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }
);

const transferRef = transfer.data.reference;
console.log("Transfer initiated:", transferRef);
<?php

$ch = curl_init();
curl_setopt_array($ch, [
  CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/crossborder/global-transfer",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode([
    "customer_uuid"        => $customerUuid,
    "amount"               => 100,
    "source_currency"      => "USD",
    "destination_currency" => "KES",
    "reference"            => "REF_TRANSFER_001",
    "reason"               => "School fees",
    "beneficiary"          => [
      "account_name"   => "Jane Doe",
      "account_number" => "0123456789",
      "network_id"     => $networkId,
      "channel_id"     => $channelId,
      "account_type"   => "bank",
      "country_code"   => "KE"
    ]
  ]),
  CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}", "Content-Type: application/json"]
]);

$transfer    = json_decode(curl_exec($ch), true);
curl_close($ch);
$transferRef = $transfer["data"]["reference"];
transfer = requests.post(
    "https://sandbox.hitchpay.ng/v1/crossborder/global-transfer",
    json={
        "customer_uuid": customer_uuid,
        "amount": 100,
        "source_currency": "USD",
        "destination_currency": "KES",
        "reference": "REF_TRANSFER_001",
        "reason": "School fees",
        "beneficiary": {
            "account_name": "Jane Doe",
            "account_number": "0123456789",
            "network_id": network_id,
            "channel_id": channel_id,
            "account_type": "bank",
            "country_code": "KE"
        }
    },
    headers={"Authorization": f"Bearer {access_token}"}
).json()

transfer_ref = transfer["data"]["reference"]
print("Transfer initiated:", transfer_ref)

Step 7 — Check Transfer Status

Poll for the final status or listen for the crossborder.transfer.success webhook.

const { data: status } = await axios.get(
  `https://sandbox.hitchpay.ng/v1/crossborder/global-transfer/${transferRef}`,
  { headers: { Authorization: `Bearer ${accessToken}` } }
);

console.log("Transfer status:", status.data.status);
<?php

$ch = curl_init();
curl_setopt_array($ch, [
  CURLOPT_URL => "https://sandbox.hitchpay.ng/v1/crossborder/global-transfer/{$transferRef}",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}"]
]);
$status = json_decode(curl_exec($ch), true);
curl_close($ch);

echo "Transfer status: " . $status["data"]["status"];
status = requests.get(
    f"https://sandbox.hitchpay.ng/v1/crossborder/global-transfer/{transfer_ref}",
    headers={"Authorization": f"Bearer {access_token}"}
).json()

print("Transfer status:", status["data"]["status"])

Summary

Step Action Endpoint
1 Enroll customer POST /v1/customer/enroll
2 Submit KYC POST /v1/customer/{uuid}/kyc
3 Await KYC approval GET /v1/customer/{uuid}/kyc
4 Discover channels & networks GET /v1/crossborder/channels + GET /v1/crossborder/networks
5 Fetch live rate GET /v1/crossborder/rates
6 Initiate transfer POST /v1/crossborder/global-transfer
7 Confirm status GET /v1/crossborder/global-transfer/{reference}

Questions? Email [email protected]. Also on GitBook.