
Integrating one payment provider is a well-understood task. Integrating three or four — each with its own API, webhook format, failure modes and settlement rules — and presenting them to your team as one coherent system is a different problem entirely. That is what a multi-gateway payment system with Node.js has to solve, and it is the core of CPCS, a payment management platform we built for an online gaming operator that processes deposits and withdrawals through CashApp, Bitcoin Lightning and other digital processors.
This post is about the payments engineering: how requests flow, how webhooks are handled safely, how gateway credentials are stored, and how a four-tier role model and full audit trail keep the money accountable. The patterns apply to any business that accepts money through more than one rail.
Why multi-gateway payment orchestration is harder than it looks
The client's situation was straightforward to describe and hard to build. They needed to handle deposits and withdrawals across multiple cryptocurrency and digital payment processors in real time, keep merchant data centralized, and make every action accountable. Specifically, the platform integrates:
- OpenNode for Bitcoin Lightning payments
- Welyds for CashApp payments
- InchasApp as an additional processor
- CashApp Pay API as part of the payment stack
Each provider confirms payments asynchronously, uses different status vocabularies and has its own retry behaviour. Without an orchestration layer, staff end up checking several dashboards to answer a simple question like “did this deposit arrive?” The goal of payment orchestration is to normalise all of that into one internal model so the rest of the system never has to care which rail a payment came through.
Architecture: Node.js, Express and TypeScript with MongoDB
CPCS uses a React.js front end and a Node.js back end on Express.js, written in TypeScript, with MongoDB and Mongoose for persistence. PM2 manages the production processes.
Why Node.js suits payment orchestration
A payment orchestrator spends most of its time waiting: on outbound API calls to processors, on inbound webhooks, on database writes. Node.js's non-blocking I/O handles that kind of concurrent, I/O-bound work efficiently. TypeScript matters just as much. When three gateways return three differently shaped payloads, strong types for each adapter and for the internal transaction model catch whole classes of bugs at compile time instead of in a reconciliation report.
The adapter pattern
The cleanest way to structure a multi-gateway system is one adapter per provider behind a common interface: create a payment request, verify an incoming webhook, map the provider's status to your internal status. The core transaction logic talks only to that interface. Adding a new processor then means writing one adapter, not touching deposit and withdrawal flows across the codebase. Axios handles outbound calls, and Joi validates every request and payload at the edge before it reaches business logic.
Why MongoDB works here
Provider responses vary in shape, and keeping the raw payload alongside the normalised transaction is valuable for debugging and dispute handling. A document store with Mongoose schemas gives you structure where you need it and flexibility where providers differ.
How to handle payment webhooks reliably
In CPCS, players submit deposit and withdrawal requests tied to specific games, and status updates flow back through real-time webhook pipelines from each processor. Webhooks are where most payment bugs live, so a few rules are non-negotiable on any build like this:
- Verify every webhook. Check signatures or shared secrets as each provider specifies. An unverified endpoint is an open invitation to mark fake deposits as paid.
- Make handlers idempotent. Providers retry. Store the provider's event or transaction ID and ignore duplicates, so the same confirmation can arrive three times and still credit once.
- Acknowledge fast, process safely. Return a success response quickly and do heavier processing after recording the event, so provider timeouts do not trigger retry storms.
- Treat status as a state machine. Pending, confirmed, failed, expired. Only allow valid transitions, and never let a late “pending” event overwrite a “confirmed” one.
- Log everything. Winston logging of inbound events and outbound calls is what lets you reconstruct exactly what happened when a user says their payment vanished.
Our payment gateway integration guide covers these webhook patterns in more depth for single-gateway setups too.
Storing gateway API keys securely per merchant
A distinctive requirement in CPCS is that each admin configures their own payment gateways. That means the platform stores many sets of third-party credentials, and a leak of any of them would be serious. The solution was encrypted API key storage per admin per payment gateway.
The general principles we follow for this kind of multi-merchant credential store:
- Encrypt keys at rest with an application-level key that never lives in the database, so a database dump alone is not enough to use the credentials.
- Decrypt only at the point of use, inside the adapter making the call, and never return decrypted keys to the front end.
- Scope access by role so only the owning admin, and the super admin where policy allows, can change a gateway configuration.
- Record every configuration change in the audit log.
Around this, the API is hardened with JWT authentication via Passport.js with refresh tokens, bcryptjs password hashing, rate limiting, XSS protection and Helmet.js security headers. None of these are exotic. All of them are expected for anything that touches money.
Role-based access and audit logging for payment operations
Payment systems need clear lines of responsibility. CPCS uses a four-tier hierarchy, with each role scoping what its dashboard shows and what actions it allows:
- SuperAdmin — a system-wide dashboard with stats across all admins and transactions, plus commission and percentage management for every admin.
- Admin — manages staff, players, games, payment methods and commission percentages within their own scope.
- Staff — views and processes deposits and withdrawals under their assigned admin.
- Player — submits deposit and withdrawal requests.
Every action across the system is recorded in an audit log with user, role, IP address and metadata. That turns questions like “who approved this withdrawal, and from where?” into a lookup rather than an investigation. For reporting and reconciliation, users, deposits and withdrawals can be exported as CSV across all role scopes, and Nodemailer handles transactional email.
In payments, an action without an audit record is an action you will eventually be unable to explain. Build the log first, not after the first dispute.
Lessons for building your own payment management system
If you are planning a payment platform that accepts money through several providers, whether for e-commerce, marketplaces or digital services, these are the decisions that matter most:
- Normalise early. Define your internal transaction model and status set before writing the first adapter.
- Check each provider's rules for your industry. Processors have acceptable-use policies and compliance requirements. Confirm your use case is supported before you build around a provider.
- Plan reconciliation from day one. Exports, raw payload storage and audit logs are what let finance staff match your records to provider statements.
- Isolate credentials per tenant. If merchants bring their own gateway accounts, encryption and scoping are core features.
- Keep the core gateway-agnostic. Adapters behind an interface are what keep the third and fourth integrations cheap.
Multi-provider payment work like this is a large part of our API development and integrations practice, often alongside Node.js back-end development. You can read the full project details in the CPCS case study.
Frequently Asked Questions
What is payment orchestration?
Payment orchestration is a layer that sits between your application and several payment providers. It routes requests to the right provider, normalises responses and webhook statuses into one internal model, and gives you a single view of all transactions regardless of which rail they used.
How do you accept Bitcoin Lightning payments in Node.js?
A common route is a processor such as OpenNode, which exposes a REST API for creating charges and sends webhooks when they are paid. Your Node.js service creates the charge, stores its ID, verifies the webhook on confirmation and updates the transaction idempotently.
How should a payment platform store third-party API keys?
Encrypt them at rest with a key held outside the database, decrypt only at the moment of use on the server, never expose them to the browser, and log every change to them.
Is MongoDB suitable for payment data?
It can be, provided you design carefully: strict schemas through Mongoose, unique indexes on provider transaction IDs for idempotency, and clear status transitions. Highly relational ledgers may favour a SQL database, so it depends on your reporting needs.
Adding a second or third payment provider, or untangling one that has grown messy? Get in touch and we will review your current flow and tell you honestly what needs rebuilding and what does not.
