https://a.storyblok.com/f/270183/1368x665/20a610fc4f/25jul_dev-blog_rich-card.jpg

How to Send RCS Standalone Rich Cards with Node.js

最終更新日 July 15, 2025

Time to read: 5 minutes

Rich Communication Services (RCS) is reshaping how businesses connect with customers, allowing you to add images, videos, and interactive elements to messages that show up natively in users' default messaging apps. One powerful feature of RCS is the standalonerich card, which combines text, images or video, and suggested replies into a single interactive message.

With global RCS adoption expanding across Android and iOS, now is the perfect time to integrate rich messaging into your applications. In this tutorial, you’ll learn how to send a standalone rich card using the Vonage Messages API in a Node.js app.

>> TL;DR: See the full working code on GitHub

What Is a Standalone RCS Rich Card?

A standalone rich card combines multiple message components like media, a title, description text, and up to four suggested replies, into a single rich message. These messages are visually engaging and offer users immediate interaction options without needing to type a reply.

A valid standalone rich card must include at least a title or mediaelement. It can also contain:

  • A description (max 2000 characters)

  • An image or video (up to 100MB)

  • Up to four suggested replies or suggested actions (not both)

Prerequisites

Before you start, you’ll need:

  • Node.js installed on your machine. Node version 22+

  • ngrok installed for exposing your local server to the internet.

  • A Vonage API account.

  • A registered RCS Business Messaging (RBM) Agent, see managed accounts below.

  • A phone with RCS capabilities for testing.

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

How to Contact Your Vonage Account Manager

In order to send and receive RCS capabilities in your Vonage application, you will need to have a registered Rich Business Messaging (RBM) agent and a phone with RCS capabilities.

Currently, RCS Messaging through Vonage is only available for managed accounts. You will need to contact your account manager to request Developer Mode activation for your RBM agent. Developer Mode allows you to test RCS messaging to allow-listed numbers before completing the Agent Verification process and launching in production.

Please contact our sales team if you do not have a managed account. 

>> Understand the difference between RCS and RBM.

How to Set Up Your Node.js Project

This guide assumes you're familiar with JavaScript and Node.js basics. 

Initialize the Project

Create a new directory and initialize a Node.js project:

mkdir rcs-standalone-richcard-node
cd rcs-standalone-richcard-node
npm init -y

Install Required NPM Packages

Install the necessary node packages with Node Package Manager (NPM):

npm install express dotenv @vonage/server-sdk

Create Your Project Files

Create the main application file and environment configuration file:

touch index.js .env

How to Configure Your Environment

In the .env file, add your Vonage credentials and configuration:

VONAGE_APPLICATION_ID=your_application_id
VONAGE_API_SIGNATURE_SECRET=your_api_secret
VONAGE_PRIVATE_KEY=./private.key
RCS_SENDER_ID=your_rbm_agent_id
PORT=3000

  • VONAGE_APPLICATION_ID: Your Vonage application ID.

  • VONAGE_API_SIGNATURE_SECRET= Your Vonage API signature secret.

  • VONAGE_PRIVATE_KEY: Your Vonage application’s private key file.

  • RCS_SENDER_ID: Your RBM SenderID (the Name of the Brand). The SenderID requires special formatting, such as not having any spaces. Check with your account manager if you’re unsure.

  • PORT: Port number for the Express server.

You will obtain your Vonage Application ID and private.key file below, in the “How to Create and Configure Vonage Application” section. Find your API Signature Secret in your developer dashboard settings.

How to Send a Standalone RCS Rich Card

The index.js file will hold your Express server the functionality to send RCS Rich Cards with suggested replies.

Load Dependencies and Initialize the Vonage Client

Add this code to your index.js file:

const express = require('express');
const fs = require('fs');
const dotenv = require('dotenv');
const { Vonage } = require('@vonage/server-sdk');
const { verifySignature } = require('@vonage/jwt');

dotenv.config();

const app = express();
app.use(express.json());

const PORT = process.env.PORT || 3000;

const VONAGE_API_SIGNATURE_SECRET = process.env.VONAGE_API_SIGNATURE_SECRET;

const privateKey = fs.readFileSync(process.env.VONAGE_PRIVATE_KEY);

const vonage = new Vonage({
  applicationId: process.env.VONAGE_APPLICATION_ID,
  privateKey: privateKey
});

Define an Express Endpoint to Send Standalone RCS Rich Cards

Next, build the /send-standalone-rich-card endpoint. This route will construct and send a rich card message using the Vonage Messages API. In this implementation, all you need to pass in your request is the recipient’s phone number.

Rich cards are composed of several elements: media, title, description, and suggested replies or suggested actions. In this example, we’re sending a single card featuring a GIF of Oscar, our office puppy, along with interactive buttons. These allow users to quickly respond with options like “Pet the puppy” or “Adopt me!”

app.post('/send-standalone-rich-card', async (req, res) => {
  const toNumber = req.body.to;

  const message = {
    to: toNumber,
    from: process.env.RCS_SENDER_ID,
    channel: 'rcs',
    message_type: 'custom', // Required for sending rich cards
    custom: {
      contentMessage: {
        richCard: {
          standaloneCard: {
            thumbnailImageAlignment: "RIGHT", // Aligns image on the right in horizontal layouts
            cardOrientation: "VERTICAL", // Stack elements vertically
            cardContent: {
              title: "Meet our office puppy!", // Main headline for the card
              description: "What would you like to do next?", // Secondary text to provide context
              media: {
                height: "TALL", // Height options: SHORT, MEDIUM, TALL
                contentInfo: {
                  fileUrl: "https://raw.githubusercontent.com/Vonage-Community/tutorial-messages-node-rcs_standalone-rich-card/refs/heads/main/puppy_dev.gif",
                  forceRefresh: false // Set to true if media changes often
                }
              },
              suggestions: [
                { reply: { text: "Pet the puppy", postbackData: "pet_puppy" }},
                { reply: { text: "Give a treat", postbackData: "give_treat" }},
                { reply: { text: "Take a selfie", postbackData: "take_selfie" }},
                { reply: { text: "Adopt me!", postbackData: "adopt_puppy" }}
              ]
            }
          }
        }
      }
    }
  };

  try {
    const response = await vonage.messages.send(message);
    console.log('Standalone rich card sent:', response);
    res.status(200).json({ message: 'Standalone rich card sent successfully.' });
  } catch (error) {
    console.error('Error sending standalone rich card:', error);
    res.status(500).json({ error: 'Failed to send standalone rich card.' });
  }
});

You can customize this endpoint by passing dynamic values like the card title, description, or list of suggested replies in the POST body. This allows you to send personalized rich cards tailored to different users or use cases.

How to Receive RCS Replies via Webhooks

When a user taps one of the suggested replies from your rich card, Vonage sends an inbound webhook to your application. This webhook contains structured data about the user's interaction, including the reply.id, which matches the postbackData value you defined earlier.

Create an /inbound_rcs endpoint to handle those replies and respond with a personalized message.

app.post('/inbound_rcs', async (req, res) => {
  // Step 1: Extract and verify the JWT signature
  const token = req.headers.authorization?.split(' ')[1];

  if (!verifySignature(token, VONAGE_API_SIGNATURE_SECRET)) {
    res.status(401).end();
    return;
  }

  // Step 2: Parse the inbound message payload
  const inboundMessage = req.body;

  if (inboundMessage.channel === 'rcs' && inboundMessage.message_type === 'reply') {
    const userSelection = inboundMessage.reply.id;
    const userNumber = inboundMessage.from;

    console.log(`User ${userNumber} selected: ${userSelection}`);

    // Step 3: Map each reply ID to a personalized confirmation
    const responseMessages = {
      pet_puppy: "🐶 Oscar loves pets!",
      give_treat: "🍪 Treat accepted! Oscar is wagging his tail.",
      take_selfie: "📸 Smile! Oscar’s photogenic and ready.",
      adopt_puppy: "Wow! Oscar is so lucky! You're a real hero 🦸"
    };

    const confirmationText =
      responseMessages[userSelection] || "Oscar appreciates the love! 🐾";

    // Step 4: Send a confirmation message back to the user
    const confirmationMessage = {
      to: userNumber,
      from: process.env.RCS_SENDER_ID,
      channel: 'rcs',
      message_type: 'text',
      text: confirmationText
    };

    try {
      const response = await vonage.messages.send(confirmationMessage);
      console.log('Confirmation sent:', response);
    } catch (error) {
      console.error('Error sending confirmation:', error);
    }
  }

  res.status(200).end();
});

What’s Happening in This Code?

First, the webhook request is verified using the JWT token to make sure it really came from Vonage. Then, we grab the user's selection using reply.id, which matches the postbackData from the rich card. Based on that ID, we pick a matching message from the responseMessages object. This is a simple example of how you can tailor the user experience based on which button they tap.

How to Define Your Express Server

At the bottom of your index.js, add this code to build your Express server.

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

And finally, run your server from the command line:

node index.js

>> See the full index.js file.

Screenshot of terminal showing Node.js logs for an RCS standalone rich card: server running on port 3000, card sent with messageUUID, user selected 'pet_puppy', and confirmation sent with a second messageUUID.Terminal output from a Node.js application sending an RCS standalone rich card, showing server activity and user interaction response with message UUIDs.

How to Expose Your Server with ngrok

To receive webhooks from Vonage, your local server must be accessible over the internet. Use ngrok to expose your server by running the following command in a separate tab from your Express server:

ngrok http 3000

Note the HTTPS URL provided by ngrok (e.g., https://your-ngrok-subdomain.ngrok.io).

You can read more about testing with ngrok in our developer portal tools.

How to Create and Configure Your Vonage Application

Now that your Node App is ready, you’ll also need to create and set up your Vonage Application. First, create your app in the Vonage Dashboard. Give the app a name and turn on the Messages capability. 

A user interface for creating a new application in the Vonage Developer dashboard. Fields include the application name, API key, authentication options, privacy settings, and messaging capabilities with inbound and status URLs. A toggle for AI data usage is set to off, and the "Generate new application" button is visible.Vonage dashboard showing the creation of a new application configured for RCS messaging.

In your Vonage application settings:

  1. Set the Inbound URL to https://YOUR_NGROK_URL/inbound_rcs.

  2. Set the Status URL to https://example.com/rcs_status.** Message statuses will be covered in a future article.

  3. Generate a public and private key by clicking the button. Ensure to move your private.key file to the project root directory (rcs-standalone-richcard-node).

  4. Save the changes.

Then link your RCS Agent by clicking the “Link external accounts” tab:

Screenshot of the Vonage dashboard where the Vonage-Node-RCS  application is linked to an RCS external account named 'Vonage,' displaying application ID, API key, and status controls.Dashboard view showing the Vonage-Node-RCS application linked to the Vonage RoR RCS external account, with voice and message capabilities enabled.

How to Test Your Node Application

Use this curl command to trigger your endpoint (replace placeholders):

curl -X POST https://YOUR_NGROK_URL/send-standalone-rich-card \
  -H "Content-Type: application/json" \
  -d '{"to": "YOUR_RCS_TEST_NUMBER"}'

On the recipient's phone, the standalone rich card with the GIF and reply buttons should appear.

Screenshot of an RCS conversation where a Vonage chatbot presents an image of a puppy and interactive options including 'Pet the puppy,' 'Give a treat,' and more. The user chooses to pet the puppy, and the bot replies with 'Oscar loves pets!Interactive RCS chat showcasing Vonage bot's playful engagement with users through options like petting, feeding, or adopting a virtual office puppy.

Conclusion

You've successfully sent an RCS standalone rich card with media, a title, description, and suggested replies using Node.js and the Vonage Messages API.

Rich cards are a great way to deliver eye-catching, interactive experiences directly inside messaging apps. In future tutorials, you’ll learn how to use carousels, track message statuses, and combine rich cards with bot logic.

Show off what you’re building with RCS and request future content on the Vonage Community Slack. You can also reach out onX (formerly Twitter). We’d love to see your RCS puppies!

Share:

https://a.storyblok.com/f/270183/384x384/e4e7d1452e/benjamin-aronov.png
Benjamin AronovDeveloper Advocate

Benjamin Aronov is a developer advocate at Vonage. He is a proven community builder with a background in Ruby on Rails. Benjamin enjoys the beaches of Tel Aviv which he calls home. His Tel Aviv base allows him to meet and learn from some of the world's best startup founders. Outside of tech, Benjamin loves traveling the world in search of the perfect pain au chocolat.