Payfrica Partner API
The Payfrica Partner API allows 3rd party integrators to leverage Payfrica's Onramp and Offramp capabilities.
Base URL
Public API
https://backend.payfrica.xyz/api/v1/partnersAuthentication
Public API Authentication (API Keys)
Used for server-to-server integration.
CAUTION
Never expose your x-partner-api-secret in frontend code. Public API requests must be made from your secure backend server.
1. Public API Endpoints
Base URL: /api/v1/partners
/ratesGet real-time exchange rates for Onramp or Offramp.
Query Parameters
Response
{
"rate": "1650.50",
"fiatAmount": "5000",
"suiAmount": "3.03"
}/offramp/instantInitiate a crypto-to-fiat transaction with immediate payout. This is currently only supported for NGN. The flow is two-step: first execute the on-chain smart contract call to lock funds and get a transaction digest, then submit that digest to this endpoint to trigger the fiat payout.
Contract IDs
0x4378b5bf4c21d313150634d529218ea11166ca113a2869a31c88deb4bb8c2417Payfrica Move package ID on Sui
0x50eee353064aa82f120bfc212486acf9bd9acf5b3a3b5aadea8284ce30a20f84Shared config object passed to the off_ramp Move function
0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDCFully-qualified coin type for USDC (6 decimals)
0x2::sui::SUIFully-qualified coin type for SUI (9 decimals)
Step 1 — Smart Contract Call (Sui)
Before calling this endpoint, execute the on-chain transaction to lock the funds. The returned digest is passed to the API in Step 2.
amount + fee as the coin value to the smart contract. The fee is automatically deducted on-chain — the contract will take the fee from the coin you provide, so ensure the coin covers the full amount plus the 0.5% fee.import { Transaction } from "@mysten/sui/transactions";
const PAYFRICA_ID = "0x4378b5bf4c21d313150634d529218ea11166ca113a2869a31c88deb4bb8c2417";
const PAYFRICA_CONFIG_ID = "0x50eee353064aa82f120bfc212486acf9bd9acf5b3a3b5aadea8284ce30a20f84";
const USDC_COIN_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
const SUI_COIN_TYPE = "0x2::sui::SUI";
// Fee: 0.5% flat for amounts 1–500 USDC or 1–500 SUI
const fee = (amount * 5n) / 1000n; // 0.5%
const tx = new Transaction();
const coinType = USDC_COIN_TYPE; // or SUI_COIN_TYPE
// Split the coin to cover amount + fee, then pass to off_ramp
// const [splitCoin] = tx.splitCoins(tx.gas, [amount + fee]); for SUI
const coins = await client.getCoins({ owner, coinType });
const coinObjects = coins.data;
// if (!coinObjects.length) throw new Error(`No ${coinType} coins found for address ${owner}`);
// Sort by balance to identify the largest coin as primary
const sortedCoins = [...coinObjects].sort((a: any, b: any) => Number(a.balance) - Number(b.balance));
const primary = sortedCoins[sortedCoins.length - 1].coinObjectId;
const others = sortedCoins.slice(0, sortedCoins.length - 1);
if (coinObjects.length > 1) {
tx.mergeCoins(
tx.object(primary),
others.map((c: any) => tx.object(c.coinObjectId))
);
}
const [splitCoin] = tx.splitCoins(tx.object(primary), [tx.pure.u64(amount + fee)]);
tx.moveCall({
target: `${PAYFRICA_ID}::payfrica_lite_v2::off_ramp`,
arguments: [
tx.object(PAYFRICA_CONFIG_ID), // Shared config object
splitCoin, // Coin covering amount + fee
tx.pure.string("NGN"), // Target fiat currency
tx.object.clock(), // Sui clock (0x6)
],
typeArguments: [coinType],
});
const result = await signAndExecuteTransaction({ transaction: tx });
const digest = result.digest; // Pass this to the API endpoint belowStep 2 — Request Body
{
"digest": "GxQ7...Sui_tx_digest",
"cryptoAsset": "USDC",
"recipient": {
"institution": "058",
"accountIdentifier": "0123456789",
"accountName": "John Doe",
"currency": "NGN",
"memo": "Instant payout"
}
}Response
{
"message": "Offramp order created",
"data": {
"rampOrderId": "uuid",
"status": "COMPLETED"
}
}/banksGet a list of supported financial institutions for payouts.
Query Parameters
Response
{
"success": true,
"data": [
{
"name": "Guaranty Trust Bank",
"bankCode": "058",
"routingKey": "058",
"logoImage": "..."
},
{
"name": "Zenith Bank",
"bankCode": "057",
"routingKey": "057",
"logoImage": "..."
},
{
"name": "Access Bank",
"bankCode": "044",
"routingKey": "044",
"logoImage": "..."
}
],
"message": "Supported institutions retrieved successfully"
}/verify-accountVerify a bank account number and retrieve the account holder's name. This endpoint uses Paystack's account resolution service.
Query Parameters
Response
{
"success": true,
"data": {
"accountNumber": "0123456789",
"accountName": "JOHN DOE"
},
"message": "Bank account verified successfully"
}/orders/:idGet the status of an order.
Query Parameters
Response
{
"id": "uuid",
"status": "COMPLETED",
"cryptoAmountReceived": "100",
"nairaPayoutCalculated": "150000"
}/verify-ninVerify the identity of a user using their National Identification Number (NIN) or Phone Number. This is required for KYC compliance.
Request Body
{
"firstname": "John",
"lastname": "Doe",
"suiAddress": "0x123...",
"nin": "12345678901",
"phone": "08012345678",
"dob": "1990-01-01",
"gender": "m"
}Response
{
"status": {
"status": "verified",
"state": "MATCH_FOUND"
},
"nin": {
"firstname": "John",
"lastname": "Doe",
"nin": "12345678901",
"phone": "08012345678",
"dob": "1990-01-01",
"gender": "m"
}
}/purchase-airtimePurchase mobile airtime for a user using a crypto payment transaction digest. Before calling this endpoint, you must first execute an on-chain transfer of the required crypto amount to Payfrica's designated utility wallet address. The backend will inspect the transaction digest, verify the recipient address, calculate the fiat equivalent value using live rates, and dispense the airtime automatically.
Contract IDs
0xa2f43d2eaccb44671f4f3a0e6788aab4e87350c901a7f40cee81009eae80d1c6Designated recipient address on Sui Network (USDC, SUI)
Request Body
{
"suiAddress": "0x123...",
"amount": 100,
"phoneNumber": "+2348012345678",
"operator": "MTN",
"digest": "GxQ7...Sui_tx_digest",
"paymentAsset": "USDC",
"network": "sui"
}Response
{
"id": "order-uuid",
"accountSuiAddress": "0x123...",
"type": "AIRTIME",
"status": "COMPLETED",
"fiatAmount": "100",
"phoneNumber": "+2348012345678",
"provider": "MTN",
"serviceId": "airtime",
"paymentAsset": "USDC",
"cryptoAmountPaid": "100000",
"exchangeRate": "1650.00",
"paymentTxHash": "GxQ7...Sui_tx_digest",
"externalReference": "vtpass-ref-123",
"createdAt": "2024-03-27T12:00:00Z"
}/purchase-dataPurchase a mobile data plan for a user using a crypto payment transaction digest. Before calling this endpoint, you must first execute an on-chain transfer of the required crypto amount to Payfrica's designated utility wallet address. The backend will inspect the transaction digest, verify the recipient address, calculate the fiat equivalent value using live rates, and dispense the data plan automatically.
Contract IDs
0xa2f43d2eaccb44671f4f3a0e6788aab4e87350c901a7f40cee81009eae80d1c6Designated recipient address on Sui Network (USDC, SUI)
Request Body
{
"suiAddress": "0x123...",
"amount": 500,
"phoneNumber": "+2348012345678",
"operator": "AIRTEL",
"dataPlan": "airtel-100MB-30",
"digest": "GxQ7...Sui_tx_digest",
"paymentAsset": "USDC",
"network": "sui"
}Response
{
"id": "order-uuid",
"accountSuiAddress": "0x123...",
"type": "DATA",
"status": "COMPLETED",
"fiatAmount": "500",
"phoneNumber": "+2348012345678",
"provider": "AIRTEL",
"serviceId": "data",
"dataPlan": "airtel-100MB-30",
"paymentAsset": "USDC",
"cryptoAmountPaid": "500000",
"exchangeRate": "1650.00",
"paymentTxHash": "GxQ7...Sui_tx_digest",
"externalReference": "vtpass-ref-123",
"createdAt": "2024-03-27T12:00:00Z"
}/variations/:serviceIDGet available data plan variations for a given VTpass service ID. Supported values: mtn-data, airtel-data, glo-data, etisalat-data.
Query Parameters
Response
{
"response_description": "000",
"content": {
"variations": {
"daily": [
{
"variation_code": "mtn-100MB-30",
"name": "MTN 100MB - 30 Days",
"variation_amount": "100",
"fixedPrice": "Yes",
"duration": "daily"
}
],
"weekly": [],
"monthly": [],
"other": []
}
}
}2. Virtual Sub-Bank Accounts
Programmatically manage virtual Naira bank accounts for your users. Base URL: /api/v1/partners
/usersGet a paginated list of users associated with your partner account.
Query Parameters
Response
{
"data": [
{
"id": "uuid",
"suiAddress": "0x123...",
"kycStatus": "VERIFIED",
"accountNumber": "1234567890"
}
],
"total": 1,
"page": 1,
"limit": 10
}/users/:suiAddress/sub-bank-account/onrampInitiate a dynamic virtual account for onramp. This creates a short-lived bank account that the user can transfer NGN to. Once received, Payfrica automatically converts the NGN to crypto and sends it to the user's wallet. Note: The amountExpected in the response includes a processing fee (0.5% for amounts < 10k, or flat ₦50 for amounts >= 10k).
Query Parameters
Request Body
{
"amount": 5000,
"cryptoAsset": "USDC"
}Response
{
"accountNumber": "1234567890",
"accountName": "PAYFRICA-ONRAMP-XYZ",
"bankName": "Safehaven Microfinance Bank",
"amountExpected": 5025,
"currency": "NGN",
"expiresAt": "2024-03-27T13:00:00Z",
"orderId": "uuid",
"transactionId": "uuid"
}/users/:suiAddress/transactionsGet the transaction history (conversions, withdrawals, deposits) for a specific user.
Query Parameters
Response
{
"data": [
{
"id": "uuid",
"type": "WITHDRAWAL",
"status": "COMPLETED",
"amount": "5000.00",
"currency": "NGN",
"reference": "WITH-123",
"narration": "Bank withdrawal",
"createdAt": "2024-03-27T12:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 10
}Webhooks
Payfrica can send real-time notifications to your system via Webhooks. All webhook requests are sent as POST requests with a JSON body and an HMAC-SHA256 signature.
Signature Verification
Each webhook request includes an x-payfrica-signature header. You should verify this signature using your Public API Key as the secret.
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const calculated = hmac.update(JSON.stringify(payload)).digest('hex');
return calculated === signature;
}Events and Payloads
virtual_account.onramp.completed
Sent when a dynamic virtual account payment is received and crypto fulfillment is completed.
{
"event": "virtual_account.onramp.completed",
"data": {
"id": "order-uuid",
"dynamicAccountId": "acc-uuid",
"type": "ONRAMP_DYNAMIC",
"amount": 5000,
"currency": "NGN",
"reference": "REF123456",
"suiAddress": "0x...",
"targetAsset": "USDC",
"cryptoAmount": "3.0303",
"txHash": "0x...",
"createdAt": "2024-03-27T12:00:00Z"
},
"timestamp": 1711545600000
}subaccount.created
Sent when a user's sub-bank account is successfully provisioned.
{
"event": "subaccount.created",
"data": {
"suiAddress": "0x...",
"accountNumber": "1234567890",
"accountName": "John Doe",
"kycStatus": "VERIFIED"
},
"timestamp": 1711545600000
}subaccount.transaction.completed
Sent when a conversion (NGN to Crypto) or withdrawal (NGN to Bank) is successful.
{
"event": "subaccount.transaction.completed",
"data": {
"id": "tx-uuid",
"type": "WITHDRAWAL",
"amount": "5000.00",
"currency": "NGN",
"reference": "WITH-123",
"suiAddress": "0x...",
"createdAt": "2024-03-27T12:00:00Z"
},
"timestamp": 1711545600000
}subaccount.transaction.failed
Sent when a transaction fails due to banking errors or payout failures.
{
"event": "subaccount.transaction.failed",
"data": {
"id": "tx-uuid",
"type": "CONVERSION",
"amount": 1000,
"currency": "NGN",
"suiAddress": "0x...",
"targetAsset": "USDC",
"error": "Payout failed",
"createdAt": "2024-03-27T12:00:00Z"
},
"timestamp": 1711545600000
}Error Responses
401 Unauthorized - Paused Partner
{
"statusCode": 401,
"message": "Partner operations are currently paused. Please contact support."
}401 Unauthorized - Unverified Partner
{
"statusCode": 401,
"message": "Partner account is not verified. Please contact support."
}403 Forbidden - Key Rotation
{
"statusCode": 403,
"message": "Partner account must be verified before API keys can be generated or rotated"
}