惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
NISL@THU
NISL@THU
WordPress大学
WordPress大学
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Threat Research - Cisco Blogs
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
Security Archives - TechRepublic
Security Archives - TechRepublic
有赞技术团队
有赞技术团队
L
LINUX DO - 热门话题
Hacker News: Ask HN
Hacker News: Ask HN
V
V2EX
G
GRAHAM CLULEY
TaoSecurity Blog
TaoSecurity Blog
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
云风的 BLOG
云风的 BLOG
Recorded Future
Recorded Future
Latest news
Latest news
The Hacker News
The Hacker News
aimingoo的专栏
aimingoo的专栏
T
Troy Hunt's Blog
S
Schneier on Security
I
Intezer
Google DeepMind News
Google DeepMind News
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threatpost
爱范儿
爱范儿
The Register - Security
The Register - Security
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
博客园 - 聂微东
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
美团技术团队
B
Blog RSS Feed

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Afriex Virtual Accounts: A Developer's Guide to Dedicated and Pool Accounts
Victory Lucky · 2026-06-20 · via DEV Community

Reconciling inbound payments seems straightforward until you have more than a handful of customers. Someone sends money to your business account, and now you need to determine who sent it, why they sent it, and what the payment was for. In most cases, this means asking customers to include a reference, which they will eventually forget.

Virtual accounts solve this problem by reversing the model. Instead of having everyone pay into a single account, you generate a unique account number for a customer, a purpose, or even a single transaction. When funds arrive, you already know exactly who the payment belongs to. There is no reference matching, no guesswork, and no manual reconciliation.

This guide explains how virtual accounts work in the Afriex Business API, including the two available account types, when to use each one, the key constraints you should understand before building, and the complete integration pattern.

Two ways to collect: Dedicated accounts vs. pool accounts

Afriex provides two distinct models for collecting inbound payments, and choosing the right one can significantly simplify your integration.

Dedicated virtual accounts (VIRTUAL_BANK_ACCOUNT) are created specifically for an individual customer. You generate them through the API, and Afriex assigns a real account number linked directly to that customer in your system. Every deposit into that account is automatically attributed to the correct customer.

Pool accounts (POOL_ACCOUNT) take a different approach. Afriex maintains a shared account that multiple customers can pay into. Deposits are reconciled using a reference value that you store and track on your side. There is no account creation step. You simply call the endpoint, receive the account details and reference, and provide them to your customer.

A simple rule of thumb:

  • Use a dedicated virtual account when you want a stable account number permanently assigned to a customer.
  • Use a pool account when you need a lightweight collection flow and are comfortable managing payment references yourself.

Important limitation: Production only

Before implementing virtual accounts, be aware of one critical limitation: virtual accounts and pool accounts are only available in production environments.

They are not supported in staging or sandbox environments. As a result, you cannot fully test the end-to-end collection flow before going live. The recommended approach is to build and unit test your integration against mocked responses, then carefully validate the complete payment flow after deployment to production.


Dedicated virtual accounts

Static vs. dynamic accounts

Dedicated virtual accounts come in two variants. The API determines which type to create based on the parameters you provide alongside currency and customerId.

Static accounts are created with a label and never expire. The account number remains valid indefinitely, making them ideal for customers who need to fund an account repeatedly over time.

Supported labels include:

SALES, OPERATIONS, PAYROLL, COLLECTIONS, VENDOR_PAYMENTS, TAX, REFUNDS, MARKETING, TREASURY, and GENERAL.

The label is purely organizational. It allows you to categorize accounts internally according to their intended use.

Dynamic accounts are created using an amount instead of a label. They expire after a short period and are designed for collecting a specific amount within a limited timeframe, such as a one-time invoice or order payment.

The label and amount fields are mutually exclusive. Including both in the same request will result in an error.

// Static virtual account, grouped under SALES
const staticAccount = await afriex.paymentMethods.createVirtualAccount({
  currency: "NGN",
  customerId: "68e6717848e1f632e9686460",
  label: "SALES",
});

// Dynamic virtual account, tied to a specific amount
const dynamicAccount = await afriex.paymentMethods.createVirtualAccount({
  currency: "NGN",
  customerId: "68e6717848e1f632e9686460",
  amount: 50000,
});

The response includes the account details you can share directly with the customer:

{
  "data": {
    "paymentMethodId": "690cc5bbe2a1143ff6070119",
    "channel": "VIRTUAL_BANK_ACCOUNT",
    "customerId": "68e6717848e1f632e9686460",
    "institution": { "institutionName": "FIDELITY BANK" },
    "accountName": "Lily New",
    "accountNumber": "3820404958",
    "countryCode": "NG"
  }
}

NGN virtual accounts and BVN requirements

When creating a static NGN virtual account, the customer must have a Bank Verification Number (BVN) on file. This is a regulatory requirement for permanently assigned Nigerian bank accounts.

If the customer does not have a BVN recorded, account creation will fail.

You can provide the BVN when creating the customer or update it later through the Customer KYC endpoint.

This requirement applies only to static NGN virtual accounts. Dynamic virtual accounts are exempt, and the requirement does not apply to other currencies.

If your product relies on static NGN accounts, collecting BVNs during onboarding will prevent failed account creation requests later in the user journey.

Per-customer account limits

Virtual account creation is subject to limits based on the combination of business, customer, and currency.

If you exceed the allowed number of accounts for a specific customer and currency, the API returns a 400 response with the error code VIRTUAL_ACCOUNT_LIMIT_REACHED.

To avoid unnecessary creation attempts, consider checking for existing accounts before creating a new one:

const existing = await afriex.paymentMethods.listVirtualAccounts({
  currency: "NGN",
  customerId: "68e6717848e1f632e9686460",
});

if (existing.data.length === 0) {
  // safe to create
  await afriex.paymentMethods.createVirtualAccount({
    currency: "NGN",
    customerId: "68e6717848e1f632e9686460",
    label: "SALES",
  });
}

A breaking change to be aware of

Previous versions of the API automatically created a virtual account when the list endpoint was called and no account existed.

That behavior has been removed.

The list endpoint is now strictly read-only. If no accounts exist, it returns a 200 response with an empty array and does not create anything.

Account creation must now be performed explicitly through the POST endpoint.

If your integration previously relied on a "get-or-create" workflow, update it to follow a clear check-then-create pattern like the example above.


Pool accounts

Pool accounts operate differently because customers are not assigned dedicated account numbers.

Instead, you call the endpoint with a country code, and Afriex returns an existing pool account along with a unique reference value.

const poolAccount = await afriex.paymentMethods.getPoolAccount({
  country: "NG",
  customerId: "6929843e2c4653277440acc0",
});

{
  "data": {
    "paymentMethodId": "69c2804b30314b491e48b305",
    "channel": "VIRTUAL_BANK_ACCOUNT",
    "customerId": "6929843e2c4653277440acc0",
    "reference": "6929843e2c4653277440acc0",
    "institution": { "institutionName": "UBA" },
    "accountName": "TEST",
    "accountNumber": "12345678",
    "countryCode": "NG"
  }
}

There are several important differences compared to dedicated virtual accounts:

  • There is no currency parameter. Currency is inferred from the country code.
  • NG automatically resolves to NGN.
  • Pool accounts are only available in countries where Afriex has configured them.
  • Requests for unsupported countries will return an error.

The most important field in the response is reference.

When a customerId is provided, the returned reference matches that customer. Store this value in your database before sharing account details with the customer.

When a deposit arrives, Afriex uses the reference to identify the payment, allowing your system to correctly attribute funds to the appropriate customer.

Feature Dedicated Virtual Account Pool Account
Account number Unique per customer Shared across multiple customers
Creation required Yes No
Best for Recurring or long-term customer payments Quick collections and one-off payments
Customer identification Automatic via account number Via stored reference value
Supports repeated top-ups Yes (static accounts) Yes, but requires reference tracking
Expires Static: No, Dynamic: Yes No
BVN required for NGN Yes (static NGN accounts only) No
Per-customer account limits Yes No customer-level creation limits
Reconciliation complexity Low Medium
Ideal use cases Wallets, subscriptions, marketplaces, recurring billing Checkout payments, invoices, temporary collections

Supported currencies

Virtual accounts are currently available across four primary currencies: USD, NGN, GBP, and EUR.

Current country coverage is shown below:

Country Currency Virtual Account Status
Nigeria NGN Live
Kenya KES Live
United States USD Live
Ghana GHS Coming soon
United Kingdom, all EU countries GBP / EUR Coming soon

If virtual accounts are not yet available in your target market, plan for an alternative collection strategy. Depending on availability, that may mean using pool accounts or another payment method until dedicated virtual accounts become available.

Reconciliation pattern

The typical lifecycle of a virtual account payment looks like this:

  1. Create the customer in your system and register them with Afriex if necessary.
  2. Create a static or dynamic virtual account for that customer.
  3. Store the paymentMethodId and accountNumber in your database.
  4. Display the account details to the customer through your checkout flow, invoice, or payment instructions.
  5. The customer transfers funds to the virtual account.
  6. Afriex sends a deposit-related webhook event when funds arrive.
  7. Your webhook handler matches the event to the stored paymentMethodId and updates the corresponding records.

This follows the same reconciliation philosophy described in the webhook integration guide. Webhooks should serve as the source of truth for payment events rather than relying on assumptions made by the application.

For pool accounts, the process is similar, but the reference becomes the primary identifier. Since multiple customers share the same account number, your webhook handler must use the stored reference to determine which customer a deposit belongs to.

Without persisting that reference beforehand, accurate reconciliation becomes impossible.

When should you use each option?

Use a dedicated virtual account when:

  • You have a known customer who will make recurring payments.
  • You want deposits to be automatically attributed to that customer.
  • You want to eliminate reference-based reconciliation entirely.
  • You are building subscription products, marketplace wallets, stored balances, or recurring billing systems.

Use the dynamic variant when collecting a specific amount within a limited time window, such as a single invoice or one-off purchase.

Use a pool account when:

  • You need to start collecting payments quickly.
  • You do not want to manage virtual account creation.
  • You want to avoid customer-level account limits and BVN requirements.
  • You are comfortable maintaining reference mappings in your own system.

A checkout experience that generates a unique reference for each order while reusing the same account details for all customers is a common use case.

Use an alternative payment method when:

  • The country or currency you need is not yet supported.
  • Virtual account coverage is still marked as "coming soon."
  • Your target corridor requires a collection method that Afriex does not currently support.

Always verify availability before implementation using the supported currencies reference.

If you'd like to see a practical implementation, the Afriex payment links guide demonstrates how to build shareable payment links backed by dynamic virtual accounts. For complete endpoint documentation and request schemas, refer to the Afriex API reference for virtual accounts and pool accounts.