
Receive an SMS Message Status with Ruby on Rails
Previously, we learned how to send SMS messages using Ruby on Rails SMS integration. However, sending an SMS shouldn't feel like sending a message in a bottle. With SMS Ruby on Rails applications, we can track message delivery and ensure reliability. Thankfully, we get that plus a lot more with the Messages API and SMS Message Status.
This tutorial will show you how to integrate SMS delivery statuses in a Ruby on Rails application using Vonage webhooks.
TL;DR Skip ahead and find all the Quickstart code on GitHub.
Prerequisites
Vonage Virtual Number
Ruby on Rails application, as described previously
To buy a virtual phone number, go to your API dashboard and follow the steps shown below.
Purchase a phone number
Go to your API dashboard
Navigate to BUILD & MANAGE > Numbers > Buy Numbers.
Choose the attributes needed and then click Search
Click the Buy button next to the number you want and validate your purchase
To confirm you have purchased the virtual number, go to the left-hand navigation menu, under BUILD & MANAGE, click Numbers, then Your Numbers
What is An SMS Message Status?
When you fire off a request to send an SMS, this is just the first step in its journey. If you make a successful SMS request, Vonage will queue the SMS while finding the best network provider to deliver the SMS. From there, the network provider will fulfill the request and send the SMS to its target recipient.
Diagram explaining SMS Message Status, showing how messages travel from an application through Vonage and carriers to the user, with delivery status callbacks.
Then, a Message Status (MS) is sent in the opposite order (Network Provider -> Vonage -> Your App) regarding whether or not the SMS was delivered successfully.
To receive this MS in your application, you will need to set up a webhook endpoint telling Vonage where to send it.
How to Publicly Expose Our Ruby on Rails App with ngrok
Vonage will need to send our message statuses to an endpoint. Our Ruby on Rails application is running in development on port 3000, so we can access it locally at localhost:3000. However that only works on our computer!
You can use ngrok to safely expose your local server publicly over HTTP. So now, in a separate tab from your rails server, open an ngrok tunnel on port 3000.
ngrok http 3000
Be sure to add your ngrok URL as a config.host in your development.rb file. We’ll use the environment variable VONAGE_SERVER_HOSTNAME
, to make our app more dynamic.
For more help, see how to get started and use ngrok in Rails.
# config/environments/development.rb
Rails.application.configure do
config.hosts << ENV['VONAGE_SERVER_HOSTNAME']
Now in our .env file, set your 'VONAGE_SERVER_HOSTNAME' to your ngrok URL.
# .env
# ngrok URL, don't include the protocol (https://)
VONAGE_SERVER_HOSTNAME=''
We’ll also need to edit our Vonage application (from previous article) in the dashboard to add the ngrok URL in our webhooks. In the Status URL field, add your ngrok URL followed by /sms_message_status
.
Vonage Messages API webhook settings showing an invalid Inbound URL error.Once you hit save ngrok will now forward requests made to the /sms_message_status endpoint to your Rails application! But there’s one small problem: our Rails application doesn’t have that end point.
How to Handle Delivery Receipt Webhooks in Rails
Now that Vonage is forwarding statuses to the /sms_delivery_receipts
endpoint, let’s update our application and create a route to handle it.
# config/routes.rb
Rails.application.routes.draw do
# For SmsMessageStatus controller, create
post '/sms_message_status', to: 'sms_message_status#create', as: :sms_message_status
...previous routes...
But what’s going to happen there? Our application is going to accept the Message Status and parse it for the relevant information. You can see what a typical Message Status looks like:
{
"to"=>"13215678899",
"from"=>"John",
"channel"=>"sms",
"message_uuid"=>"93cc5afe-2824-4d40-97b1-e6f6ec00249e",
"timestamp"=>"2025-02-23T14:52:53Z",
"status"=>"delivered",
"destination"=> {"network_code"=>"42507"}
}
The most important field is the status because it tells you whether your message was delivered and, if not, what went wrong. See more information about Message Status webhooks.
We’re going to create some logic to update our SMS status based on the received message statuses. Let’s generate a new controller in the command line to handle this.
rails g controller SmsMessageStatus create
Our controller has a single, simple create
method. First, the method parses our message status and extracts the message_uuid; it uses this foreign ID to find the SMS in our local database. It then updates the status for this SMS to the current status we received in the message status.
class SmsMessageStatusController < ApplicationController
skip_before_action :verify_authenticity_token
def create
SmsMessage.where(message_uuid: params[:sms_message_status][:message_uuid])
.update_all(status: params[:sms_message_status][:status]) if params[:sms_message_status][:message_uuid]
# Return an empty HTTP 200 status
head :ok
end
end
You can now send an SMS and watch your rails server to see that the webhook is called, forwarded, and ActiveRecord updates the database!
Console log showing the processing of an SMS delivery receipt in a Vonage Rails application, including a database update query.
Conclusion
You did it! You learned how to expose your Ruby on Rails application to listen for a webhook via ngrok, updating your database in the process! We’ll continue to build on this application by accepting incoming SMS in the next post.
If you have any questions or suggestions for more Ruby content, send us a message over on the Community Slack. You can also stay in the loop on our developer content and events on X, formerly known as Twitter.
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.