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.