Payfrica Logo
Payfrica

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/partners

Authentication

Public API Authentication (API Keys)

Used for server-to-server integration.

x-partner-api-key:pk_live_...
x-partner-api-secret:sk_live_...

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

GET/rates

Get real-time exchange rates for Onramp or Offramp.

Query Parameters

typestring
onramp | offramp (default: onramp)
currencystring
NGN, KES, GHS (default: NGN)
amountnumber
Fiat amount (for onramp) or Crypto amount (for offramp)
cryptostring
USDC | SUI (default: USDC)

Response

{
  "rate": "1650.50",
  "fiatAmount": "5000",
  "suiAmount": "3.03"
}
POST/offramp/instant

Initiate 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

PAYFRICA_ID0x4378b5bf4c21d313150634d529218ea11166ca113a2869a31c88deb4bb8c2417

Payfrica Move package ID on Sui

PAYFRICA_CONFIG_ID0x50eee353064aa82f120bfc212486acf9bd9acf5b3a3b5aadea8284ce30a20f84

Shared config object passed to the off_ramp Move function

USDC_COIN_TYPE0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC

Fully-qualified coin type for USDC (6 decimals)

SUI_COIN_TYPE0x2::sui::SUI

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

Fee note: You must pass 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 below

Step 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"
  }
}
GET/banks

Get a list of supported financial institutions for payouts.

Query Parameters

currencystring
NGN (default)

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"
}
GET/verify-account

Verify a bank account number and retrieve the account holder's name. This endpoint uses Paystack's account resolution service.

Query Parameters

accountNumber*string
The bank account number to verify
bankCode*string
The Paystack bank code (use paystackCode from /banks endpoint)

Response

{
  "success": true,
  "data": {
    "accountNumber": "0123456789",
    "accountName": "JOHN DOE"
  },
  "message": "Bank account verified successfully"
}
GET/orders/:id

Get the status of an order.

Query Parameters

id*string
The UUID of the order

Response

{
  "id": "uuid",
  "status": "COMPLETED",
  "cryptoAmountReceived": "100",
  "nairaPayoutCalculated": "150000"
}
POST/verify-nin

Verify 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"
  }
}
POST/purchase-airtime

Purchase 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

SUI_RECIPIENT_ADDRESS0xa2f43d2eaccb44671f4f3a0e6788aab4e87350c901a7f40cee81009eae80d1c6

Designated 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"
}
POST/purchase-data

Purchase 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

SUI_RECIPIENT_ADDRESS0xa2f43d2eaccb44671f4f3a0e6788aab4e87350c901a7f40cee81009eae80d1c6

Designated 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"
}
GET/variations/:serviceID

Get available data plan variations for a given VTpass service ID. Supported values: mtn-data, airtel-data, glo-data, etisalat-data.

Query Parameters

serviceID*string
The VTpass service ID to query variations for. Must be one of: mtn-data, airtel-data, glo-data, etisalat-data

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

GET/users

Get a paginated list of users associated with your partner account.

Query Parameters

pagenumber
Page number (default: 1)
limitnumber
Items per page (default: 10)

Response

{
  "data": [
    {
      "id": "uuid",
      "suiAddress": "0x123...",
      "kycStatus": "VERIFIED",
      "accountNumber": "1234567890"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 10
}
POST/users/:suiAddress/sub-bank-account/onramp

Initiate 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

suiAddress*string
The Sui wallet address of the user to receive the crypto

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"
}
GET/users/:suiAddress/transactions

Get the transaction history (conversions, withdrawals, deposits) for a specific user.

Query Parameters

pagenumber
Page number (default: 1)
limitnumber
Items per page (default: 10)

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"
}
© 2026 Payfrica. All rights reserved.