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"
  }
}

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.