In this article, I will be showing you how to build a simple crypto-to-cash application with focus on how to sync the services of Paystack and Blockradar such that immediately your users receive crypto the Naira variant is immediately received in your local bank account.
Prerequisite
In this article you are expected to know Javascript, Typescript, Node, Express, Postgres (or any database of choice) and little knowledge of Javascript but not mandatory. This article is for developers who are looking to integrate blockradar into their work.
What is a blockradar
Blockradar is a wallet infrastructure that gives you addresses, wallets and other blockchain infrastructure without you having to focus on blockchain complexities and implementation details.
In our article we will be focusing on the wallets, address and webhook implementation details. Although blockradar provides far more than this.
How blockradar works
When you create and account and signup you have an interactive dashboard. You will need to create what is called a master wallet. As the name implies it is the master wallet where gas fees will be withdrawn from and where assets can be swept into (autoSweep). The master wallet comes with a master address and are linked to a child address. In declaration of a master wallet we define the network (TRON, ERC 20) etc and assets that can be sent to the address.
Gas fees
These are transaction fees, usually your bank charges you some charges for transfer or card maintenance. For you to move money on the blockchain you will be gas fees. Now blockradar offers us gasless withdraw this means we handle the fees from our matter wallet (this is recommended) or users handle it. For gasless withdraw you need to have some native tokens in the master wallet. Gas fees are usually very small.
Autosweep
When we create a master wallet/address we can create sub/children addresses that is a child of the master wallet. For security a lot of times we want the account to be swept that is each money that comes into an address to be held in the master wallet. In our application each user will have a child address. This means funds are held centrally doesn’t mean that wallet owners assets is mixed up or lost.
Custodial and Non Custodial wallets
This means we choose not show users their pins (private keys, seed phrases) such that outside our app user don’t have access to their addresses. While non custodial is the direct opposite meaning that outside our application users have access to everything outside the app since it’s on chain. In order to manage the focus of this article we will be dealing with custodial wallets meaning everything is managed on blockradar and your code base.
Multi Chain
In blockradar the addresses we get are multi-chain compatible, let me dive more on this. A blockradar address is like an account number it is where people send money to. Let’s say we generate an EVM (Ethereum virtual machine) compatible address, it means we can use that address to receive all assets built on top of the EVM Engine like Base, BNB Smart Chain, Polygon, Arbitrum, Avalanche, Lisk, etc.) use this same Ethereum engine.
Getting started with blockradar
1. Create an account
Head over to blockradar create an account fill in all the required details (<1 min), you maybe required to link to Google authenticator after signing up.
2. Create a master wallet
Click on the New master wallet button to create a master wallet
For the purpose of this article, I will be using TRC 20 as my network and USDT since it’s the most popular stable coin.
3. Getting the master wallet details
After creation click on the wallet card you will be routed to a wallet detail pages. Which will most likely be empty.
At the bottom of the screenshot below you will see an API key and a wallet ID. Please copy them as they will be needed. Just below the Title and description you will get the address of the master wallet. Also copy it and save.
4. Setting configurations
I want to configure auto sweep from the dashboard, this can also be configured via the code. This configurations sets all receivables from the child wallet to be swept, stored and controlled in the master.
5. Funding the master wallet
We will need some test tokens to fund the tron wallet, get test tokens here
Copy your master wallet address from the screenshot below.
Then you can fund it with some USDT and use for transactions.
What is paystack
Paystack is a payment service provider and infrastructure that powers payment over the internet. In this article we will be using paystack as the fiat provider. Such that immediately users receive money in their wallet they receive the naira variant.
Paystack dashboard
Create a paystack account with this link. Fill out all the information then after creation of the account and other necessary details go the settings section on the sidebar and then click on the API keys and webhook section then copy the test keys and put out the details on your .env file
Code block
I liked to model my code in classes and services so this article is not excessively long here is a link to the full code. I will focus mainly on the Services and routes.
BlockradarService class
In this class BlockRadar generates wallet addresses and detects deposits (webhook), CoinGecko provides real-time conversion rates (you can add to it to suit your business decisions), Paystack handles bank account verification and transfers.
import axios from 'axios';
import { PaystackService } from './paystackService';
import dotenv from 'dotenv';
dotenv.config();
const API_KEY = process.env.BLOCKRADAR_API_KEY;
const walletId = process.env.TRON_ARTICLE_WALLET_ID;
const http = axios.create({
baseURL: 'https://api.blockradar.co/v1',
});
http.defaults.headers.common['x-api-key'] = API_KEY;
export interface CreateAddressParams {
name?: string;
metadata?: Record<string, any>;
disableAutoSweep?: boolean;
enableGaslessWithdraw?: boolean;
}
export interface WebhookEvent {
[key: string]: any;
}
export class BlockradarService {
static async createChildAddress(params: CreateAddressParams) {
try {
const response = await http.post(`wallets/${walletId}/addresses`, {
disableAutoSweep: params.disableAutoSweep ?? false,
enableGaslessWithdraw: params.enableGaslessWithdraw ?? true,
metadata: params.metadata || {},
name: params.name,
});
return { ok: true, data: response.data };
} catch (error: any) {
console.error(
'BlockRadar Create Address Error:',
error?.response?.data || error.message
);
throw new Error('Failed to create address');
}
}
static async getChildWallets() {
try {
const response = await http.get(`wallets/${walletId}/addresses`);
return { ok: true, data: response.data };
} catch (error: any) {
console.error(
'BlockRadar Get Wallets Error:',
error?.response?.data || error.message
);
throw new Error('Failed to fetch wallets');
}
}
static async processWebhookEvent(event: any) {
try {
console.log('Webhook received:', event);
if (event.event !== 'deposit.success') {
return {
ok: true,
message: 'Event ignored (not deposit.success)',
};
}
//Here you could send a mail saying transaction has been received in your wallet
const deposit = event.data;
const { amount, currency, metadata } = deposit;
const rateResponse = await axios.get(
'https://api.coingecko.com/api/v3/simple/price',
{
params: {
ids: 'tether',
vs_currencies: 'ngn',
},
}
);
const usdtToNaira = rateResponse.data.tether.ngn;
console.log('1 USDT =', usdtToNaira, 'NGN');
const amountInNaira = parseFloat(amount) * usdtToNaira;
console.log(`Converted amount: ₦${amountInNaira}`);
const userId = metadata.user_id;
const accountNumber = metadata?.account_number;
const bankCode = metadata?.bank_code;
const verifiedAccount = await PaystackService.verifyAccount(
bankCode,
accountNumber
);
if (!verifiedAccount?.account_name) {
throw new Error('Invalid recipient bank details');
}
const recipient = await PaystackService.createRecipient(
verifiedAccount.account_name,
verifiedAccount.account_number,
bankCode
);
const transfer = await PaystackService.transferMoney(
amountInNaira,
recipient.recipient_code,
`Deposit payout for user ${userId}`
);
/// You can store the details of the transfer here in your db with a schema of name
/// Transaction amount, Recipient, bank details, transaction, a state of successfull etc
return {
ok: true,
message: 'Deposit processed and payout sent successfully',
data: {
deposit,
conversion: { rate: usdtToNaira, amountInNaira },
transfer,
},
};
} catch (error: any) {
console.error('Error processing webhook:', error.message);
return {
ok: false,
error: error.message,
};
}
}
}
1. Setting up the configs
We start by importing the dependencies we’ll need:
- axios – for making HTTP requests to APIs.
- PaystackService – a helper class that talks to the Paystack API.
-
dotenv – used to securely load environment variables from a
.envfile.
Then, we configure our environment variables: Then, we configure our environment variables.
2. Creating an axios instance
Instead of repeating setup code in every request, we create an Axios instance for BlockRadar.
const http = axios.create({
baseURL: 'https://api.blockradar.co/v1',
});
http.defaults.headers.common['x-api-key'] = API_KEY;
This means every call made with http will automatically include your BlockRadar API key and the correct base URL.
3. Creating a child address
static async createChildAddress(params: CreateAddressParams) { ... }
This method creates a child wallet address under your main BlockRadars wallet. Each user can have their own address for receiving assets/crypto. If successful, BlockRadar returns the new address and its details.
4. Fetching child wallets
static async getChildWallets() { ... }
This simply retrieves all existing child addresses linked to your main wallet handy for displaying balances or tracking deposits.
5. Processing web hook events
The most powerful feature of this service is the processWebhookEvent method. It handles real-time deposit notifications from BlockRadar and sends automatic payouts to users through Paystack.
Here is what happens step by step below:
-
Listen for deposits:
if (event.event !== 'deposit.success') { return { ok: true, message: 'Event ignored (not deposit.success)' }; }BlockRadar sends various events, but we only care about
deposit.success, which tells us money has just arrived in the wallet. -
Extract deposit details
const deposit = event.data; const { amount, currency, metadata } = deposit;We pull out key information like:
a. The amount received
b. The currency (e.g., USDT)
c. The metadata, which may include the user ID and bank details -
Convert crypto to naira
const rateResponse = await axios.get( 'https://api.coingecko.com/api/v3/simple/price', { params: { ids: 'tether', vs_currencies: 'ngn' } } ); const usdtToNaira = rateResponse.data.tether.ngn; const amountInNaira = parseFloat(amount) * usdtToNaira;We use CoinGecko’s API to get the live exchange rate of 1 USDT in Naira and multiply it by the deposited amount. This gives us the payout value in NGN.
-
Verify the users bank details
const userId = metadata.user_id; const accountNumber = metadata.account_number; const bankCode = metadata.bank_code; const verifiedAccount = await PaystackService.verifyAccount( bankCode, accountNumber );The bank information (sent earlier when creating the wallet address) is verified through Paystack to ensure it’s valid.
-
Create a Paystack Recipient
const recipient = await PaystackService.createRecipient( verifiedAccount.account_name, verifiedAccount.account_number, bankCode );In Paystack, before you can send money to someone, you must first create a recipient profile. This method handles that automatically.
-
Send the Payout
const transfer = await PaystackService.transferMoney( amountInNaira, recipient.recipient_code, `Deposit payout for user ${userId}` );Once the recipient is created, we trigger a transfer to the verified bank account — paying the user their Naira equivalent.
-
Return Success
return { ok: true, message: 'Deposit processed and payout sent successfully', data: { deposit, conversion: { rate: usdtToNaira, amountInNaira }, transfer, }, };If anything fails along the way (like an API error), the function catches it and returns a clear error message instead of crashing the app.
Paystack service class
The PaystackService class handles all interactions between your application and the Paystack API.
It allows you to verify bank accounts, create transfer recipients, and send payouts automatically all essential parts of managing fiat transactions in your app.
This service is used by BlockradarService to verify user details and send Naira payouts after detecting deposits.
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const http = axios.create({
baseURL: 'https://api.paystack.co/',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_PRIVATE_KEY}`,
},
});
http.defaults.headers.common['Authorization'] =
'Bearer ' + process.env.PAYSTACK_PRIVATE_KEY;
http.defaults.headers.common['Content-Type'] = 'application/json';
export class PaystackService {
static async getBankList() {
try {
const response = await http.get('/bank');
return response.data.data;
} catch (error: any) {
console.error(error.response?.data || error.message);
throw new Error('Failed to fetch bank list');
}
}
static async getBankCodeByName(bankName: string) {
const banks = await this.getBankList();
const bank = banks.find((b: any) =>
b.name.toLowerCase().includes(bankName.toLowerCase())
);
if (!bank) {
throw new Error(`Bank not found: ${bankName}`);
}
return bank.code;
}
static async createRecipient(
name: string,
accountNumber: string,
bankCode: string
) {
try {
const response = await http.post('/transferrecipient', {
type: 'nuban',
name,
account_number: accountNumber,
bank_code: bankCode,
currency: 'NGN',
});
return response.data.data;
} catch (error: any) {
console.error(error.response?.data || error.message);
throw new Error('Failed to create recipient');
}
}
static async transferMoney(
amount: number,
recipientCode: string,
reason = 'Payout'
) {
try {
const response = await http.post('/transfer', {
source: 'balance',
amount: amount * 100,
recipient: recipientCode,
reason,
});
return response.data.data;
} catch (error: any) {
console.error(error.response?.data || error.message);
throw new Error('Failed to transfer money');
}
}
static async verifyAccount(bankCode: string, accountNumber: string) {
try {
const response = await http.get(
`/bank/resolve?account_number=${accountNumber}&bank_code=${bankCode}`,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_PRIVATE_KEY,
},
}
);
// returns { account_name, account_number, bank_details }
return response.data.data;
} catch (error: any) {
console.error(
'Paystack API Error:',
error.response?.data || error.message
);
// Handle specific Paystack errors
if (error.response?.data) {
const paystackError = error.response.data;
throw new Error(paystackError.message || 'Failed to verify account');
}
throw new Error('Failed to verify account');
}
}
}
-
Setting up the configs
We start by importing the required dependencies:
- axios – for making HTTP requests to Paystack’s REST API.
-
dotenv – to load Paystack secret keys from the
.envfile securely.
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const PAYSTACK_SECRET_KEY = process.env.PAYSTACK_SECRET_KEY;
const http = axios.create({
baseURL: 'https://api.paystack.co',
headers: {
Authorization: `Bearer ${PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
});
Here, we create a reusable http instance that automatically includes your Paystack secret key in every request so you never have to repeat it in each method.
-
Methods in the class
- Verify bank account
(verifyAccount): This method ensures that the provided bank code and account number belong to a real bank account. If successful, Paystack returns details like the account name and bank name, which you can use to confirm the user’s identity before proceeding. - Create Transfer Recipient
(createRecipient): Before you can send money through Paystack, you must register the recipient (i.e., the user’s verified bank account). This method creates a transfer recipient and returns arecipient_codethat can be used to initiate payouts. - Transfer Money
(transferMoney): This method sends a payout to a verified recipient using Paystack’s transfer endpoint. 5The amount should be in kobo (₦1 = 100 kobo). You can easily convert before sending.
- Verify bank account
In summary, BlockRadar detects a deposit then User’s bank details (stored in metadata) are verified via verifyAccount, A Recipient is created using createRecipient a transfer is initiated using transferMoney then transaction details stored in the database.
| Feature/Method | Description |
|---|---|
| verifyAccount | Checks if a user’s bank account is valid |
| createRecipient | Registers a verified bank account for transfers |
| transferMoney | Sends Naira payouts to the registered account |
| Secure Config | All secrets stored in .env, never hard-coded |
| Reusable HTTP Client | Axios instance configured with base URL and headers |
Webhooks
A critical part of a blockradar and paystack are web hooks, webhooks let us know when something happens on chain like a transfer is successful, a transaction failed transfer is received and other events.
Webhooks allow you to set up a notification system that can be used to receive updates on certain requests made to the Blockradar API.
A webhook is simply a URL (endpoint) in your backend that BlockRadar calls automatically whenever a specific event happens.. In our existing implementation our webhook let’s us know when a transaction has been successful then we get the data of the successful transaction then we can perform a transfer via paystack.
Let us think of it this way, Instead of you constantly asking BlockRadar, “Has a deposit arrived yet?”
BlockRadar calls you immediately when something changes. That “call” comes in the form of an HTTP POST request with JSON data that describes the event.
In our BlockradarService class we have processWebhook event as earlier described when defining the class details. You link the method to handler to a route handler and then connect it to blockradar via the dashboard. Note: The endpoint of your webhook must be POST.
Linking the master wallet to your web hook endpoint
Login in to your dashboard, then click on the developers (In the sidebar).
However you will notice that your development is usually on localhost and blockradar can’t access the localhost of your machine. To expose your local server to the internet for testing purposes, consider using a tool like ngrok. These tools allow you to see what the webhook payload looks like and simulate webhook events locally before deploying your application to a live environment. Using these tools, you can ensure that your webhook integration is functioning correctly and handle any issues in a controlled, local environment before moving to production.
After installing ngrok you run the command below
ngrok http 3005
You will get an output like this below.
Now notice your localhost is linked to a remote ngrok url→ https://a713e25fe660.ngrok-free.app (this is for my local)
The route of my webhook endpoint for blockradar is on /api/webhooks/blockradar so I can now link https://a713e25fe660.ngrok-free.app/api/webhooks/blockradar on my blockradar dashboard.
import express from 'express';
import { BlockradarService } from './services/blockradarService';
const router = express.Router();
router.post('/blockradar/webhook', async (req, res) => {
const event = req.body;
const result = await BlockradarService.processWebhookEvent(event);
if (!result.ok) {
return res.status(500).json({ ok: false, error: result.error });
}
// This is very important to blockradar doesn't keep sending post requests.
// Sending a 200 response means the data was received.
res.status(200).json({ ok: true, message: result.message });
});
export default router;
Every time a deposit occurs, BlockRadar sends the event payload here →
Your service converts the crypto → verifies the bank → sends the payout → logs the transaction.
In order to confirm the webhook works, I want to send USDT to a child wallet and log the details via my webhook.
I have received a USDT of a 1000 and I log the post event body on my IDE console. Which means our Webhook works.
Now once assets are sent to a child address it is received via the webhook and the fiat variant send. In order to make handling stages of the assets transfer let’s follow this flow once assets are sent to a blockradar and received via the webhook you can create a model in your DB for transactions where you store the transaction state or send emails.
- Add a column to your Transactions table tag it
isAssetReceivedonce set to true the user receives a mail that asset has been received in his account. - Also we will need to store the transaction state of the paystack transfer and we will do that via web hooks as well.
Paystack webhooks
Here you confirm your payments via webhook and based of this you can decide to set the status of the payments as successful and as well notify the user.
Paystack has the following transfer states, for the course of this article we will focus only on the transfer.success
transfer.success |
This is sent when the transfer is successful |
|---|---|
transfer.failed |
This is sent when the transfer fails |
transfer.reversed |
This transfer is reversed. |
import { Request, Response } from 'express';
import { BlockradarService } from '../services/blockradar';
export const webhookHandlers = {
async processPaystackWebhook(req: Request, res: Response): Promise<void> {
try {
const { event, data } = req.body;
console.log(`Paystack webhook received: ${event}`);
switch (event) {
// ✅ Successful transfer
case 'transfer.success':
await this.handleTransferSuccess(data);
break;
// 🧩 Optional: add more cases (e.g. charge.success, invoice.payment_failed)
default:
console.log(`Unhandled Paystack event: ${event}`);
break;
}
res.status(200).json({
ok: true,
message: 'Paystack webhook processed successfully',
});
} catch (error: any) {
console.error('Paystack Webhook Error:', error?.message || error);
res.status(500).json({
ok: false,
error: 'Failed to process Paystack webhook',
});
}
},
// ✅ Handle successful transfer
async handleTransferSuccess(data: any) {
console.log('✅ Transfer Success:', data.reference);
// Example: update your transaction table
// await db.query(
// 'UPDATE transactions SET status = ?, updated_at = NOW() WHERE reference = ?',
// ['success', data.reference]
// );
// Example: notify user (email/SMS/queue)
// await NotificationService.sendPaymentConfirmation(userId, data.amount);
},
};
Then you can link the webhookHandlers to the /api/webhooks/paystack route and then connect it to our paystack dashboard via the ngrok instance we previously created.
Note: For transfers to work you have to use a paystack real account with live keys, however the implementation details are the same.
Conclusion
In this article we have learned how to create a backend that is able to create a wallet for users and send fiat instantly.
Blockradar offers wallets, addresses, webhooks and many more, combining with paystack fiat features offers a powerful crypto to cash system. Although the regulations of Nigeria doesn’t allow Paystack interface with crypto but this implementation can be used cause from a delivery standpoint the details of crypto aren’t spelt out. Attached here is the github repository
References
I will love to connect with you, these are my social media details. Github. Linkedin. X
Hope you get to read my next article. Thank you!















Top comments (1)
Real nice article, Making use of two services like these independently is brilliant. I have a question about retry mechanisms incase of paystack failures. I feel that is a part to be really careful with in this model.
Off the top of my head ill say job queues (BullMQ) for the retry logic if any of the transactions fails. minimum of 3 retries I think.
What do you think?