
How to Send RCS Standalone Rich Cards with Node.js
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.
DT API Account
To complete this tutorial, you will need a DT 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 DT 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
express: Creates the web server
dotenv: Loads your environment variables
@vonage/server-sdk: Sends messages through the Vonage Messages API
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.
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.
Vonage dashboard showing the creation of a new application configured for RCS messaging.
In your Vonage application settings:
Set the Inbound URL to https://YOUR_NGROK_URL/inbound_rcs.
Set the Status URL to https://example.com/rcs_status.** Message statuses will be covered in a future article.
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).
Save the changes.
Then link your RCS Agent by clicking the “Link external accounts” tab:
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.
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:
)
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.