A few days ago, I was buying an item from one of the Indonesian marketplaces. Although I live far away, I can still order items online. It’s so easy now to do transactions online. However, one thing that intrigued me was the payment method I used on that e-commerce platform. It’s called a Virtual Account.
I know there are many payment methods globally. However, we will discuss one of the most popular Indonesian payment methods. I don’t know the history of this virtual account concept, even though I previously worked on a payment gateway; I just thought it was common.
The Virtual Account is similar to a real bank account but is formatted like a Debit/Credit card. It appears as a series of numbers, for example, 8123991200002342, with the first 4-5 digits usually representing an identifier assigned by the bank issuer to the merchant.
The merchant then resells virtual accounts starting with 8123xxxxxxxx. When you purchase on an e-commerce platform (the merchant), instead of sending funds directly to the merchant’s bank account, you pay into a “Virtual Account,” such as 8123991200002342.
You can view the account details through Inquiry using ATM, Mobile Banking, Internet Banking, or other platforms. Additionally, you can transfer money to this Virtual Account just like a real bank account. The bank holds the money and links it to the “merchant,” like a Payment Gateway or e-commerce site. In this article, I will focus on the Payment Gateway.
The high-level overview, something like the one below:
To illustrate a real scenario, let’s say there’s a buyer—we can call him Agus for simplicity. Agus wants to buy an item; let’s say he’s purchasing RTX 5090 in a marketplace we’ll refer to as GreenShop. When Agus wants to check out, he will select a payment method; of course, there will be many options available, for example.
Credit/Debit Card
Virtual Account
etc.
Then, Agus will choose a Virtual Account (VA). What happens in the background is that GreenShop will call their payment gateway partner, for example, let’s say it: GatePay. As a payment gateway (PG), GatePay will have two options (usually).
Direct API Call to Bank.
Host to Host Connection to Bank
These two concepts were quite common back then in Indonesia, at least from 2017 to 2024; I don’t know, maybe they have newer technology now. But let’s assume the concept is still relevant. I will explain these two concepts below.
Direct API Call to Bank Architecture
First, we will discuss this architectural type, which we assume GatePay will use for a direct API call to the bank (as you can see in the first diagram above).
Back to our story above, the Rich Bank will generate the VA number in its system and send it to GatePay, which will forward it to GreenShop. Finally, GreenShop will inform Agus, “Here’s your virtual account number, transfer to this number, eg, 0812345678xxxx, around IDR 70,000,000.”
{
"vaNumber":"0812345678xxxx",
"amount" : 70000000
"currency" : "IDR"
}After that, Agus will transfer to the VA number (owned by the bank), which is essentially like a bank account, but it’s temporary. Additionally, since Agus is only making a one-time transaction, GreenShop will request a Closed VA from the PG, and PG from the Bank.
In VA, there are ussually 2 types of VA
Open VA
An Open VA can always receive any amount of money.
Ussually, for types of businesses like credit-based services or SaaS with subscriptions, they usually offer the Open VA.
Closed VA.
A Closed VA will reject the transfer if it doesn’t match the total amount it was configured for.
Usually, for online shopping, people use the Closed VA.
*Disclaimer: different banks offer different approaches, but generally, based on my experience, it should work like this.
Finally, after the bank receives the money from Agus, Rich Bank will notify GatePay through a webhook. GatePay will record that transaction in its ledger and send a webhook to Greenshop. In the final step, Greenshop will mark the invoice for Agus as “paid.” After that, all processes related to delivery/logistics will be dispatched, which we won’t discuss here.
Advantages and Benefits
I like this architecture because it clearly defines the domain boundaries between PG, banks, and customers (marketplace, etc.). PG focuses on handling and routing requests to all connected banks, banks focus on managing the VA, money transfers, etc., and customers concentrate on their businesses.
What I can see as advantages of this architecture, or have experienced in my career, are
The separation of concerns is clear. Domain boundaries are clearly defined. PG focuses on routing the payments.
Loosely coupled
Tradeoffs and Challenges
Of course, there will always be trade-offs between architectures. I know there are more trade-offs, but the most common ones (or those I have experienced) are:
Less Secure compared to Host-to-Host
This architectural type is the most common concept in software engineering, where a business provides a public API that users can access. In this case, the Bank offers a public API that anyone can easily use. However, compared to Host-to-host architecture, where everything is securely connected within protected networks, this architecture type is less secure. That is also why some large payment gateways prefer a Host-to-Host option with their partners; besides the strong partnership with the bank, it also provides a secure, robust transaction connection with the bank.
Mitigation
To address this issue, both the bank and the payment gateway (GatePay) must consistently update and assess their security practices. They should follow security principles, conduct regular evaluations, and perform compliance checks to reduce the risk of security breaches. For example, using short-lived access tokens, automatic key rotation, IP address whitelisting, etc.
Audit hell.
Let’s imagine a scenario, for example, when there’s a webhook failure. From Rich Bank to GatePay, GatePay won’t receive any payment notifications. Some banks, of course, implement retry practices, but some follow their own standards. From GatePay's perspective, it’s hard to maintain all the behaviors of the bank because different banks may have different approaches to how their systems are designed.
There was no standardization at the time (before the Central Bank of Indonesia introduced the BI Snap). Even if, let’s say, there was standardization among Indonesian banks, there were still other banks in different countries we needed to keep up with. It’s exhausting; sometimes, engineering involves providing technical support, conducting manual recon, and handling mundane tasks that engineers shouldn’t be paid for.
One example I remember from some banks in Indonesia (around 2019) is that instead of retrying webhooks, they would have group discussions about missing payment webhooks, sharing CSV files in the group chat.
This is quite common; working with some major banks in Indonesia has taught me a lot about this. It’s just typical, and there are no resolutions to make it better. There’s no API for us to check the payment status, no retries from the bank side, just sharing CSV files in the WhatsApp group.Mitigation:
To mitigate the risk of this issue, the Bank needs to implement a retry mechanism that attempts retries up to N times. Additionally, it should provide a dashboard for the PG to trigger the resending of events for all payment transactions. This feature will alleviate the manual reconciliation efforts handled by the technical support teams from the Bank and the payment gateway.
Idempotency challenge
If not handled correctly, this could lead to more disasters. If, let’s say, some banks are sending duplicate events or maybe there are some system restarts, etc. PG must be able to identify webhook idempotency. If not, it will be like receiving double payments.
In our example case of GatePay, let’s say they don’t handle double payment events. Instead, GatePay's system assumes every event is one legitimate transaction. In this case, GatePay might settle double the payment that Greenshop requested.
I remember fixing this issue with a payment gateway one time. The system doesn't manage idempotency request handling. Instead, every webhook received is recorded as a new payment. If the bank sends multiple payment events, the system records them as multiple transactions. It was pretty messy. We ended up paying the customer (e.g., Greenshop) at least twice for every payment event received from the bank.Mitigation
Of course, handling the request using an idempotency key (could be `transactionId` for example) on the PG side, and sending events with an at least once mechanism from the Bank side is a good option here.
Host-to-Host with Bank Architecture
The second option, host-to-host, typically requires a significant amount of “credibility” to access. Banks are unlikely to offer a partnership (host-to-host) to just any random company. You either need to have someone in the government who is truly influential, or your executives must be prominent figures in the industry, or your company must be the industry leader. Otherwise, obtaining it will be challenging. They suggest that anyone requesting host-to-host integration should use their public API instead.
Usually, the process is like,
Agreement, Contract sign-off, etc
API documentation retrieved from the bank. Some banks are already modern enough to use REST, but I had one bank provide us with a SOAP API, and it’s in XML. While everyone is talking about gRPC, GraphQL, etc., but you have to write a SOAP server for the host-to-host.
API Development
Network Testing / SIT (System Integration Testing)
Staging Testing
User Acceptance Testing with real money.
The process for this is somewhat tiring, as it requires close collaboration between PG and the Bank we integrated with.
How does it work for VA?
If we go back to the example I mentioned above, let’s say that GatePay (our example PG above) can connect host-to-host with some banks. The difference with this concept is that GatePay will have more control over its Virtual Account.
In this architecture, the VA source of truth will be on the GatePay side. The Rich Bank will always check the GatePay server for the inquiry process. If GatePay returns negative, the user won’t see the VA number as active. They also won’t be able to see the details. Imagine it when you transfer money to another bank account; usually, you can see who the beneficiary is, even when you transfer from an ATM, mobile banking, etc. But in this case, it will ask PG to check if that VA exists instead of looking up from the bank database.
This concept differs from the Direct Public API call described in the abovementioned option. In the first option, the bank handles the VA generation logic. The bank will only reference its database and process the requests during the inquiry process. The dependency is isolated within the bank, which sends the PG notification only after the VA has received money.
But in this host-to-host architecture, if the PG is down, the bank also won’t be able to process the user request. So, there is also a strict SLA between the bank and PG regarding availability when validating the VA number.
Advantages and Benefits
Even with the complexity and numerous compliance checks required to build this system, I believe this is actually the preferred architecture from PG's perspective. This architecture allows PG to customize the VA number generation. Furthermore, PG can easily handle many operations for the VA, such as closing or expiring the VA.
This also means that the PG, already standing as a “trusted” partner of the bank, allows them to connect host-to-host to handle payments.
Tradeoffs and Challenges
Compliance check
In my experience, the most challenging part of this setup is ensuring our system complies with the regulations and compliance checks. The PG system must have certain licenses to process payments, such as PCI DSS. Some regulations depend on the specific country (e.g., data must be stored within the country’s region, and payment data cannot leave the region, etc.).Security and Network Peering
The bank is very strict regarding host-to-host connections, particularly concerning security and network peering. Based on my experience, setting up a host-to-host connection with a major bank in Indonesia can take up to 1-2 months due to the numerous compliance requirements, testing, and network configurations that must be completed.Delayed Webhook
The standard and most disliked issue for engineers (at least during my time working on building the PG) is dealing with delayed webhooks. A virtual account usually has an “expiration.” However, let’s say that the PG has already set the VA as expired, but somehow the bank sends a payment event one hour later.
This creates a headache: Is this a legitimate payment, or is it not? How should we handle these events? Should we re-activate the expired VA and mark it as paid afterwards? Or should we move it to manual recons (dead letter queue, etc)
Or forward it to GreenShop, “Hey, your customer A actually transferred the money 1 hour ago, but it was delayed by the bank.”?
This situation occurs frequently, especially during payroll week, the busiest week for banks, and sometimes the bank sends a late payment. Many GreenShop users usually complain, for example, “I already transferred to this VA number, but why is my invoice still showing unpaid?” etc.
Final words
As a user, I really like the Virtual Account payment method because it often has a high payment limit. It works like another temporary bank account. Therefore, it naturally has a larger payment limit when used for transactions. Plus, it's cheaper. For example, I want to buy a new RTX 5090 for IDR 70M, but the fee will only be around IDR 6500 FLAT. Imagine doing this with a credit card. Sometimes the fees are based on a percentage, like a 0.05% fee. The higher the payment amount, the more it costs me to pay.
However, as a payment gateway, this feature likely does not generate significant profits. The cost of running it is not yielding much profit from the fees gained from it.
Building the Virtual Account payment feature as a payment gateway is probably not that hard now. It's significantly better now compared to when I was working on this feature. Many banks are already undergoing digital transformation, where their APIs are capable of serving merchant and customer needs directly, and with better API documentation, among other enhancements. Not to mention, with the new API standardization of BI SNAP for the Indonesian Banking Industry.
I don’t have any updates in this industry; it’s been almost 3 years since I left it. However, I believe there are generally two types of integrations with banks regarding Virtual Account payments: Direct Public API calls and Host-to-Host architecture. Both have their own trade-offs and benefits, as I’ve listed in this article above.
It’s fascinating to see the advancements in technology. Recently, QR-code payments have also been regulated and widely accepted in Indonesia and across Southeast Asia. I will likely share more about it in a future article—please let me know if you’re interested in reading it, and I will write it as soon as possible.
These notes were written in collaboration between Iman Tumorang (Staff Engineer at Veriff.com) and Yong Cheng (Principal Engineer at Govtech.sg) to explain how the Virtual account payment technology works behind the scenes. Also, we’re open to fractional / consultant roles across payments/fintech, platform engineering, or general technology strategy. Contact us at softwarearchitect.id for inquiries or to find architects/experts who match your problem/business requirements.






