# Real-Time Payment Systems: How to Build Instant Payment Solutions with Rapyd

**URL:** <https://community.rapyd.net/t/real-time-payment-systems-how-to-build-instant-payment-solutions-with-rapyd/59779>\
**Category:** 👓 Blog\
**Tags:** tech-blog, real-time-payments\
**Created:** [May 13, 2025, 1:00pm UTC](https://community.rapyd.net/t/real-time-payment-systems-how-to-build-instant-payment-solutions-with-rapyd/59779 "2025-05-13T13:00:02Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Community\_Team](https://avatars.discourse-cdn.com/v4/letter/c/46a35a/32.png) [@Community\_Team](https://community.rapyd.net/u/Community_Team)\
**Post date:** [May 13, 2025, 1:00pm UTC](https://community.rapyd.net/t/real-time-payment-systems-how-to-build-instant-payment-solutions-with-rapyd/59779/1 "2025-05-13T13:00:03Z")

</div>

![Banner that reads, Real-Time Payment Systems How to Build Instant Payment Solutions with Rapyd](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/3/5/3539013db0a4e24410c73038f56d5d6ddfce7f2f.png)

By: **Gift Uhiene**

Real-time payment (RTP) systems enable instant fund transfers between bank accounts, making them essential for modern financial applications. For each transaction, funds and payment information are exchanged and settled within seconds of the transaction initiation. RTP service is continuous 24/7 and available all year round.

[Rapyd](https://www.rapyd.net/) is a global fintech-as-a-service provider that enables businesses to perform instant fund transfers in local currencies and maintain digital wallets globally. With its extensive API offerings, developers can integrate Rapyd finance capabilities into their applications to effortlessly manage cross-border payments, multicurrency treasury, card issuing, fraud protection, and compliance.

In this tutorial, you’ll learn how to build an RTP-enabled Node.js app using Rapyd and WebSockets, covering setup, API integration, and transaction handling.

You can explore the complete code and follow along with the implementation via [GitHub](https://github.com/Rapyd-Samples/rtp-rapyd).

## Prerequisites

Before getting started, you should have the following:

- A [Rapyd account](https://dashboard.rapyd.net/sign-up); if you’re signing up for the first time, you’ll need to verify your email and complete the required verification steps
- [Node.js](https://nodejs.org/) installed on your system
- A basic understanding of JavaScript, React.js, and APIs

## Getting Rapyd API Credentials

Log in to the [Rapyd dashboard](https://dashboard.rapyd.net/) and navigate to the **Developers \> API access control** section:

 ![Get Rapyd credentials](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/0/f/0f54205b8f190904f6727259074e275857739dc3.jpeg)

Store these keys securely as you’ll need them to authenticate API requests:

 ![Rapyd credentials](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/a/b/abf765870f33aaed52fcdbecdcdaf6581da219af.png)

## Building a Real-Time Payment Backend

In this section, you’ll build a backend system that integrates the Rapyd API and WebSockets to create a real-time payment flow. The system will allow users to create wallets, initiate transfers, and receive real-time updates on transfer statuses. Here’s what we’ll cover:

- **Setting up the Rapyd API client** to send secure requests
- **Creating a WebSocket server** to broadcast real-time updates to clients
- **Creating wallet controllers** to communicate with Rapyd APIs
- **Building the Express server** to tie everything together

### Project Setup

Clone the starter branch of the project with the command below:

```shell
git clone --branch starter --single-branch https://github.com/Rapyd-Samples/rtp-rapyd.git

```

This is the structure of the `backend` directory:

```markdown
rtp-rapyd/
    │── backend/
    │ │── node_modules/
    │ │── src/
    │ │ │── config/
    │ │ │ ├── rapydClient.js # Handles API requests to Rapyd
    │ │ │── controllers/
    │ │ │ ├── walletController.js # Logic for wallet
    │ │ │── routes/
    │ │ │ ├── walletRoutes.js # Defines API routes for wallet actions
    │ │ │── server.js # Main entry point
    │ │ │── websocket.js # WebSocket logic for real-time updates
    │ │── .env # API keys and secrets
    │ │── package.json # Project dependencies
    │ │── README.md # Documentation

```

### Installing Dependencies

Navigate to the `backend` directory in your terminal and install dependencies:

```sh
cd backend
npm install

```

Then, run the following command in the `backend` directory of your terminal to start the server:

```sh
npm run dev

```

## Configuring the Rapyd API Client

To interact with Rapyd’s API, you’ll need a client that handles authentication, request signing, and HTTP communication.

Rapyd’s API requires specified header parameters for each request to verify that the requester is an authorized user and to protect against data tampering.

Go to the `backend/src/config/rapydClient.js` file and include the code below to set up a Rapyd API client. This will allow you to securely authenticate and make HTTP requests to Rapyd’s API:

```javascript
require("dotenv").config();
const https = require("https");
const crypto = require("crypto");

const accessKey = process.env.RAPYD_ACCESS_KEY;
const secretKey = process.env.RAPYD_SECRET_KEY;

async function makeRequest(method, urlPath, body = null) {
  try {
    const httpMethod = method.toUpperCase();
    const httpBaseURL = "sandboxapi.rapyd.net";
    const salt = generateRandomString(8);
    const idempotency = new Date().getTime().toString();
    const timestamp = Math.round(new Date().getTime() / 1000);
    const signature = sign(httpMethod, urlPath, salt, timestamp, body);

    const options = {
      hostname: httpBaseURL,
      port: 443,
      path: urlPath,
      method: httpMethod,
      headers: {
        "Content-Type": "application/json",
        salt: salt,
        timestamp: timestamp,
        signature: signature,
        access_key: accessKey,
        idempotency: idempotency,
      },
    };

    return await httpRequest(options, body);
  } catch (error) {
    console.error("Error generating request options", error);
    throw error;
  }
}

function sign(method, urlPath, salt, timestamp, body) {
  try {
    let bodyString = body ? JSON.stringify(body) : "";
    let toSign =
      method.toLowerCase() + urlPath + salt + timestamp + accessKey + secretKey + bodyString;

    let hash = crypto.createHmac("sha256", secretKey);
    hash.update(toSign);
    return Buffer.from(hash.digest("hex")).toString("base64");
  } catch (error) {
    console.error("Error generating signature", error);
    throw error;
  }
}

function generateRandomString(size) {
  return crypto.randomBytes(size).toString("hex");
}

async function httpRequest(options, body) {
  return new Promise((resolve, reject) => {
    try {
      let bodyString = body ? JSON.stringify(body) : "";

      const req = https.request(options, (res) => {
        let response = { statusCode: res.statusCode, headers: res.headers, body: "" };

        res.on("data", (data) => {
          response.body += data;
        });

        res.on("end", () => {
          response.body = response.body ? JSON.parse(response.body) : {};
          if (response.statusCode !== 200) return reject(response);
          return resolve(response);
        });
      });

      req.on("error", (error) => reject(error));

      req.write(bodyString);
      req.end();
    } catch (err) {
      reject(err);
    }
  });
}

module.exports = { makeRequest };

```

The code above is from the [Node.js example](https://docs.rapyd.net/en/request-signatures.html) on the Rapyd documentation. The `sign` function generates a signature for each request using your `accessKey` and `secretKey`, ensuring secure communication with the API. To prevent duplicate transactions, every request includes a unique `idempotency` key.

Before moving forward, create a `.env` file in the root of the `backend` directory to store your Rapyd credentials:

```env
RAPYD_ACCESS_KEY=your_access_key
RAPYD_SECRET_KEY=your_secret_key

```

## Setting Up WebSockets

WebSockets enable bidirectional communication between the server and clients, allowing real-time updates without the need for constant polling. Implementing WebSockets in this project will keep the connection open so that when a change happens, the server will immediately update users without them having to refresh the page. To implement this, update `backend/src/websocket.js` file with the code below:

```javascript
const WebSocket = require("ws");

let wss;

exports.setupWebSocketServer = (server) => {
  wss = new WebSocket.Server({ noServer: true });

  server.on("upgrade", (request, socket, head) => {
    wss.handleUpgrade(request, socket, head, (ws) => {
      wss.emit("connection", ws, request);
    });
  });

  wss.on("connection", (ws) => {
    console.log("✅ New WebSocket connection");

    ws.on("message", (message) => {
      console.log("📩 Received:", message);
      ws.send(`Server received: ${message}`);
    });

    ws.on("close", () => console.log("❌ Client disconnected"));
  });
};

exports.notifyUsers = (transaction) => {
  if (!wss) return;

  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify(transaction));
    }
  });
};

```

The `setupWebSocketServer(server)` function initializes a WebSocket server that handles upgrades from an HTTP server, listens for client connections, and processes incoming messages. The `notifyUsers(transaction)` function will broadcast real-time updates to all connected clients by sending transaction data as a JSON string.

Update `backend/src/server.js` file with the code below to create a server for the application:

```javascript
const express = require("express");
const http = require("http");
const cors = require("cors");
const { setupWebSocketServer } = require("./websocket");
const walletRoutes = require("./routes/walletRoutes");

const app = express();

const server = http.createServer(app);

app.use(cors());
app.use(express.json());
app.use("/api/wallet", walletRoutes);

setupWebSocketServer(server);

server.listen(4000, () => console.log("Server running on port 4000"));

```

## Rapyd Wallet: Managing Real-Time Transactions and Fund Transfers

When building a real-time payment system, one of the key components you’ll need is a reliable and flexible wallet system. [Rapyd Wallet](https://docs.rapyd.net/en/rapyd-wallet.html) allows businesses and developers to create, manage, and transfer funds between wallets in real time by acting as a financial hub where money can be moved across borders, currencies, and payment methods.

Some of its key features include:

- **Multicurrency support:** Each wallet can hold multiple accounts, each with its own currency and balance.
- **Custom branding:** You can brand the wallets with your own logo and design, making it feel like a native part of your application.
- **Client and user wallets:** A two-tier structure with client wallets for businesses and user wallets for individuals (such as sellers or employees).
- **FX capabilities:** Rapyd Wallet integrates with Rapyd’s foreign exchange (FX) features, allowing you to convert currencies at competitive rates.
- **Split payments:** You can split payments between multiple wallets, which is useful for marketplaces or platforms with shared revenue models.

### Building the Wallet Controller

To manage all wallet-related operations, let’s build a wallet controller. This controller will handle interactions with the Rapyd API and centralize the logic for wallet management.

Open `backend/src/controllers/walletController.js` and update `createWallet` with the code below to implement the function to handle wallet creation and initial funding using Rapyd’s API:

```javascript
exports.createWallet = async (req, res) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { first_name, last_name, email, type } = req.body;

    const timestamp = Date.now();
    const emailPrefix = email.split("@")[0];
    const ewalletReferenceId = `${emailPrefix}_${timestamp}`;

    // Construct the wallet creation payload
    const walletData = {
      first_name,
      last_name,
      ewallet_reference_id: ewalletReferenceId,
      email,
      type,
      contact: {
        email,
        first_name,
        last_name,
        contact_type: `${type === "company" ? "business" : "personal"}`,
        country: "SG",
        address: {
          name: `${first_name} ${last_name}`,
          line_1: "123 Main Street",
          country: "SG",
        },
        country: "SG",
        date_of_birth: "2000-11-22",
        metadata: {
          merchant_defined: true,
        },
      },
    };

    const walletResponse = await makeRequest(
      "POST",
      "/v1/ewallets",
      walletData
    );
    const walletId = walletResponse.body.data.id;

    const fundResponse = await makeRequest("POST", "/v1/account/deposit", {
      ewallet: walletId,
      currency: "SGD",
      amount: 20,
    });

    res.json({
      wallet: walletResponse.body.data,
      initial_deposit: fundResponse.body,
    });
  } catch (error) {
    handleError(res, error);
  }
};

```

This code performs the following steps:

- Validates the request using `express-validator` to ensure all required fields are present.
- Extracts user details from the request body.
- Generates a unique `ewallet_reference_id` by combining the email prefix and a timestamp.
- Constructs a `walletData` object containing user details, contact information, address, and metadata. Some fields, like the country (`SG`), are hard-coded but can be customized.
- Creates a wallet by sending a POST request to Rapyd’s `/v1/ewallets` endpoint. The response includes a `walletId` that uniquely identifies the wallet.
- Funds the wallet with 20 SGD by making a deposit request to `/v1/account/deposit`. This step is specific to sandbox mode, as real deposits require actual funding sources in production.
- Returns a JSON response with the wallet details and deposit transaction.

Next, in the same file, update the `retrieveWallet` function to fetch details about a user wallet:

```javascript
exports.retrieveWallet = async (req, res) => {
  try {
    const accountId = req.params.id;

    if (!accountId) {
      return res.status(400).json({ error: "Account ID is required" });
    }

    const result = await makeRequest("GET", `/v1/ewallets/${accountId}`);
    res.json(result.body.data);
  } catch (error) {
    handleError(res, error);
  }
};

```

The function above extracts `accountId` from the request parameters and makes a GET request to Rapyd’s API to retrieve a wallet’s details.

### Transferring Funds Between Wallets

Rapyd allows [funds transfers](https://docs.rapyd.net/en/transfer-funds-between-wallets.html) between wallets; if the destination wallet does not have the specified currency, an account for that currency is automatically created. When a transfer is initiated, the status is marked as “PEN” until accepted by the transferee, using the [Set Transfer Response API](https://docs.rapyd.net/en/set-transfer-response.html). Unaccepted transfers can, in turn, be canceled by the sender using the same API.

To initiate a peer-to-peer wallet transfer, update the `transferFunds` function in the `backend/src/controllers/walletController.js` file:

```javascript
exports.transferFunds = async (req, res) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { senderWalletId, receiverWalletId, amount } = req.body;

    const response = await makeRequest("POST", "/v1/ewallets/transfer", {
      source_ewallet: senderWalletId,
      destination_ewallet: receiverWalletId,
      currency: "SGD",
      amount,
    });

    const transactionId = response.body.data.id;

    if (!transactionId) {
      return res.status(500).json({ error: "Transaction ID not found." });
    }

    notifyUsers({
      sender: senderWalletId,
      receiver: receiverWalletId,
      id: transactionId,
      currency: "SGD",
      amount,
      status: "pending",
      variant: "TRANSFER_FUNDS",
    });

    res.json({
      message: "Transfer initiated. Waiting for confirmation...",
      transactionId,
    });
  } catch (error) {
    handleError(res, error);
  }
};

```

The function sends a request to Rapyd’s `/v1/ewallets/transfer` endpoint to transfer funds between wallets, using SGD as the currency. It extracts the transaction ID, notifies all WebSocket clients about the transfer, and responds to the client confirming the request was initiated.

Finally, update the `respondToTransfer` function in the same file to enable both the sender and transferee to respond to a transaction:

```javascript
exports.respondToTransfer = async (req, res) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { status, id } = req.body;

    if (!["accept", "decline", "cancel"].includes(status)) {
      return res.status(400).json({
        error: "Invalid status. Must be 'accept', 'decline', or 'cancel'.",
      });
    }

    const response = await makeRequest(
      "POST",
      "/v1/ewallets/transfer/response",
      {
        id,
        status,
      }
    );

    if (response.body.status.status !== "SUCCESS") {
      return res.status(500).json({ error: `Failed to ${status} transfer.` });
    }

    notifyUsers({
      sender: response.body.data.source_ewallet_id,
      receiver: response.body.data.destination_ewallet_id,
      id,
      currency: response.body.data.currency_code,
      amount: response.body.data.amount,
      status:
        status === "accept"
          ? "Accepted"
          : status === "decline"
          ? "Declined"
          : status === "cancel"
          ? "Cancelled"
          : status.charAt(0).toUpperCase() + status.slice(1),
      variant: "RESPONDS_TO_TRANSFER",
    });

    res.json({
      message: `Transfer ${status}ed successfully.`,
      status: response.body.data.status,
    });
  } catch (error) {
    handleError(res, error);
  }
};

```

The function extracts the `status` and `id` from the request, validates the `status` (`accept`, `decline`, or `cancel`), and sends a request to Rapyd’s `/v1/ewallets/transfer/response` endpoint to update the transfer. It also notifies the WebSocket clients about the transaction.

## Connecting the Frontend

Now that the backend is set up, let’s connect the frontend application to interact with the backend and provide real-time updates to users.

> **Note:** Most of the frontend components are already provided in the starter project, so this example doesn’t focus on building them from scratch. Instead, you’ll concentrate on integrating the frontend with the backend and enabling real-time functionality.

### Installing Dependencies

In another terminal, navigate to the `frontend` directory and install dependencies:

```sh
cd frontend
npm install

```

Run `npm run dev` to run the React.js project.

### The createWallet Function

Go to the `frontend/src/components/create-wallet-form.jsx` file and update `createWallet` with the code below. This function sends a POST request to the backend to create a wallet and stores the wallet ID in local storage:

```jsx
const createWallet = async (e) => {
  e.preventDefault();
  setLoading(true);
  try {
    const response = await axios.post(
      "http://localhost:4000/api/wallet/create",
      userData
    );
    localStorage.setItem("ewallet", response.data.wallet.id);
    await fetchUser();
  } catch (error) {
    console.error("Failed to create wallet:", error);
  } finally {
    setLoading(false);
  }
};

```

### The transferFunds Function

Let’s implement the function to transfer funds between wallets. Update `transferFunds` in the `frontend/src/components/transfer-form.jsx` file. This function sends a POST request to the backend to initiate a transfer between two wallets:

```javascript
const transferFunds = async (e) => {
  e.preventDefault();
  setLoading(true);

  try {
    await axios.post("http://localhost:4000/api/wallet/transfer", {
      senderWalletId: walletData.id, 
      receiverWalletId: receiverWallet, 
      amount: Number(amount), 
    });
  } catch (error) {
    console.error("Failed to transfer funds:", error);
  } finally {
    setLoading(false);
  }
};

```

### The respondToTransfer Function

Finally, let’s add the function to respond to transfer requests. Update `respondToTransfer` in the `frontend/src/components/transaction-list.jsx` file. This function sends a POST request to the backend to approve or reject a transfer:

```jsx
const respondToTransfer = async (transactionId, action) => {
  setLoading(true);

  try {
    await fetch("http://localhost:4000/api/wallet/respond-transfer", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        id: transactionId,
        status: action,
      }),
    });
  } catch (error) {
    console.error("Failed to respond to transfer:", error);
  }

  setLoading(false);
};

```

## Final Result

To bring everything together, here’s a demonstration of the app in action. The GIF below showcases the real-time payment features, including:

- Initiating a fund transfer between wallets
- Receiving real-time updates on transaction statuses
- Accepting, declining, or canceling transfers dynamically

![Real-time payment demonstration](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/9/d/9dd3a617e793884859dd530555862e7b18405ec2.gif)

With that, you’ve successfully implemented a dynamic, real-time payment system using Rapyd’s API and WebSockets.

## Conclusion

In this article, you learned how to build a real-time payment system using the Rapyd API and WebSockets. From setting up the backend to handling wallet creation, wallet-to-wallet transfers, and real-time updates, you now have the tools to create a seamless payment experience for users. By integrating Rapyd’s powerful API and leveraging WebSocket communication, you can ensure that both senders and receivers receive instant feedback on their transactions so your application is dynamic and responsive.

Rapyd’s global payment capabilities make it an excellent choice for developers and businesses looking to implement secure and scalable payment solutions in their applications. Whether you’re building a peer-to-peer payment app, an e-commerce platform, or a financial service, Rapyd provides the infrastructure to handle transactions efficiently. To explore more features and capabilities, visit the [Rapyd API documentation](https://docs.rapyd.net/en/index-en.html) and start building your next fintech solution today.

For further learning, check out Rapyd’s [developer community resources](https://community.rapyd.net/) and [sample projects](https://github.com/Rapyd-Samples).
