Skip to main content


Developing with Bandwidth: Ruby 101

Come hangout with the Bandwidth Developer Experience team as we dive into Ruby and demo what’s new with our Voice & Messaging SDKs and APIs. We’ll also share what’s new with our Developer Experience, plus give you a sneak peek at our roadmap for future Ruby SDK support.

What you’ll learn

  • Install and configure your development environment in Ruby
  • Setup your Bandwidth Account to start using the new APIs
  • Send and receive group MMS
  • Manage inbound and outbound phone calls

Webinar Transcript:

PRESENTER: All right. Hey there, everybody. We're just going to run through a quick example using Bandwidth Ruby SDK. And we're going to go through using this Bandwidth example repo here. So under github we have bandwidth slash examples. And then if I go to the root here, I can find the webinar example we're going to be working with today right here. And so we're going to go through a couple of pre-reqs. And we're going to build this app. We need some environment variables, we need to set up some callback URLs. But let's go over the app's requirements, so to speak. We're going to, say with your new Bandwidth phone number, if you text that phone number the word "dog," you're going to receive a picture of a dog sent back. So like an auto response with an MMS. If you text that number any other phrase besides dog, you're going to get a generic response. We didn't actually do the current datetime. We need to update that. And then if you call that phone number, you're going to be asked to play a little arithmetic game. So we're going to go through today and build this application using the Bandwidth Voice and Messaging APIs. So to get started, let's head to our Bandwidth dashboard. And will take us to that. And first thing we need to do is actually-- sorry, back up a little bit. We need to make sure we have the application ngrok installed. So if we head to we can see what this is. Bandwidth requires any callbacks for inbound messages or inbound phone calls or outbound status events be sent to a publicly facing URL. And that's what ngrok is great for. It's going to allow us to set up our localhost, run just like we normally would if we were testing locally, but then also get a publicly facing URL so we can let Bandwidth servers reach our localhost without setting up any reverse proxies or deploying to the cloud. Obviously there's some side effects with this, but overall it's a great little application for rapid development. So, this is what we're going to be using for that publicly facing the URL, just to reiterate. Continuing that, we're logged into Bandwidth dashboard. We're going to need a couple of resources. The first thing that we're going to need is a voice and messaging application. I already have two created and I have their application IDs assigned to my environment variable. So I'm not going to actually update my variables there, but I'm going to show you how to create a new voice application and a new messaging application. Once we have that, ngrok domain's spun up. So I actually already have ngrok up and running right here. And you kind of see some of my testing here. So if I grab this forwarding address right here, this is going to be my callback URL. So this I can post it here. We'll get failed tunnel completion. There's nothing running. And you can see there's my browser making a get request to that ngrok URI. So with that, first thing we do is we're going to call this Ruby Voice Webinar, just to demonstrate what a voice application would look like. I also would recommend turning off ad blockers using the Bandwidth dashboard. We don't serve ads or anything, but we do use some third party libraries to provide nice tooltips, like this right here, that are blocked when you have an ad blocker on. A little overly aggressive, but you can turn that off. We're not serving ads, we're not collecting data, it's just our parties there. So we'll do Ruby Webinar Voice. If I can spell today. And we're going to allow this to be a POST request. And let's post or paste our ngrok URI there. And if we see our inbound voice callback will be sent to this path here. Paste that in. Clear it out. And any time we have an inbound call, as we can see here, the call initiated event will be sent to this URI. For any other non actual events, like a disconnect, we're going to send that to the call status callback URL. So this is going to be things that tell you like the total time duration of the call. Just more information about what happened with the call, not necessarily anything you can do moving forward. So go ahead and create that application. And once you do that, you'll see your application ID. Go ahead and copy this. And you'll need this to actually run the app. We'll do the same thing for messaging. So we'll do Ruby Webinar Messaging. And same thing for the callback URL. No. Let me try again. Here we are. And this, as we look in our docs, is going to be callback messaging. Clear that up. Messaging is a little bit different than voice in that both outbound delivery receipts and inbound messages are both sent to this callback URL. We'll talk about that when we actually start writing the code, but basically any time you send a text message with Bandwidth APIs, we're going to send you a callback to your server to find in this callback URL letting you know what happened with that message. Was it successful? Was it errored out for whatever reason? We're going to give you all the details to this URL here. So when you click to create the application, we're going to copy that application ID. And we're going to need this to send out our messages. The next thing we need to do is go ahead and get a location. A location is a logical grouping of phone numbers and applications to maintain that phone number to callback URL relationship. So if we head to our account, and we head to our locations, we're going to add a location. I already have a sub account, but we're going to add a location under my sub account. And this will be Ruby Webinar. We don't need a description. We do need to change the protocol to HTTP Voice. And quite straightforwardly, we're going to grab the Ruby Webinar Voice. We don't really care about origination or termination settings for HTTP Voice. These are mainly network service settings. If you have a network services account, you know what those are. If not, really not important for the HTTP Voice product. We're going to enable SMS. I don't really care about texting to or from toll free or short codes, but I'm going to enable those. So this enables communication with these phone numbers if you need to use shortcode to send text messages to your users, contact sales, contact support, we can get you set up with that. Next, I need to select my application. Ruby Webinar Messaging. And I'm going to be using V2 Messaging. Just got to make sure I have that enabled. Next, need to enable MMS. As you can see, I don't really have the option to choose anything else. It's going to inherit all the settings to be the same. So we're going to go ahead and create that location. Now that we have-- oop, didn't mean to make it default. I'm going to update mode. Oh, goodness. Update this to make it default. Clear my notifications. And so now I have this new webinar here. You notice I don't have a phone number on this account or this location. So I'm going to head to the numbers tab here. I'm just going to search for 919, nothing too fancy. Grab that. And just grab that first number. We're going to continue. I do need to make sure this is under my sub account and under my location. And I'm going to purchase this phone number. So at this point, I can text this phone number if I needed to and it will send callbacks to that ngrok URL that we defined earlier. But I need a couple more bits of information before we really get going. I need to head back to my applications tab and I need to look at these APIs credentials. This is going to be a token and secret pair, specifically for messaging. So anything that you'd see under this API credentials that are token and secrets is exclusive to the messaging product and will not work for any other endpoints within Bandwidth. So I'm just going to go ahead and create a new pair. As you can see, they are generated server side and displayed here. One thing to note is that once you close the screen, any secrets that were displayed previously are not going to be recoverable. So if you do close the screen before you save your token and secret pair, they're gone at that point. Bandwidth cannot recover that secret for you. You're just best off just deleting that pair and generating a new one, like I did here. Just because I've exposed this on the webinar, I need to go ahead and delete this pair right now. And then finally, for the other API, specifically voice and anything for account management, like if you wanted to automate this application location creating, you would need to go to Account. And you're going to go to Users. And specifically, you're going to need to create an API on the user. You can see I've got an example that I have here where it's still basic user, don't need to make it an admin, but the role here is API. This prevents the password resets from happening to your account, but it does not allow you to log into the dashboard. So it's nice for a service account, which is what we're going to be using throughout the webinar examples today. But it is worth keeping in mind you can also use your login username and password if the dashboard access is set to both. However, we strongly recommend you create an API User to use as a service account, specifically for voice and messaging or for voice and phone number management. So with all that said and done, I now have my ngrok URL. I have my token and secret pair for sending and receiving text messages. I have my account, my API User account that I will need to create phone calls and manage phone numbers. We can actually start diving into the code. So I'm going to head back to the applications tab and we're really just going to dive right in. This is using the Sinatra framework. It's a relatively simple framework that we have set up. I've got my Slack open on accident. Let's minimize that. Let's try again. I've got all of my environment variables that I need defined within this README. But I have this set already. We've got our callback URLs as we talked about earlier, and we have our little app requirements defined under What You Can Do. So we're going to hop into the code-along. And this is starting from this raw application here. We can pop on over to our tab. And if we were to run this app right now, it's running on port 4567, which is what this is going to be porting to us if we went to localhost. 4567. There's your "hello, world," so we can see our app is indeed returning the hello. If we were to wave, and go back to here, we can rerun this. And if we refresh this page, there we are, "Hi, hello, world." So we've got our app up and running. Relatively trivial, not doing a whole lot right now. OK. Let's bounce back over to our code-along. And I'm just going to slide this over to the side. So the first thing that we need to do is actually just go ahead and initialize the Bandwidth Client. These are my includes right here. I'm including Bandwidth Messaging and Bandwidth Voice. I also have this just big block of variable grabs based on my environment variables. So at this point, I'm going to have the account ID, user password, token, secret, applications, all that fun stuff, now saved from my environment variables for me to use throughout this application. We're going to throw a little error. If for whatever reason you weren't able to get that set, it's going to say, hey, please, check that for us. Then we're going to go ahead and define the Bandwidth Client. This is going to take in our user and password for voice, basic off. And as I mentioned before, messaging does take the token and secret. So we do have two separate forms of authentication, one for voice, one for messaging. Voice uses that API service account I showed you in your account users tab. Messaging uses the token and secrets that I showed you in the applications tab under API credentials. When you've selected the application type as messaging, you can grab those. And again both of those are read only. Read once. So once you generate that token secret pair, you need to save it, otherwise it's gone forever. So if we save our app now, we just have the Bandwidth Client ready to go, and we have our environment variables. So let's look at our callbacks for messaging. So for this guide, we're only going to be responding to messages, we're not going to be creating new messages, so to speak, where any response to a text message is a new message, but it was always initiated by someone texting us first. So we're going to go ahead and paste in our deserialization. So we have a nice little API helper that will serialize this into JSON. And then we can actually create a Ruby class from this hash right here. That will give us a very nice tooltips and make it really easy to access parts of that message, that callback message. So one thing I do want to demonstrate is the documentation at for messaging and callback. If we look at Incoming Group, we're going to assume everything could be a group message, we can see that the callbacks come in as a list or an array. There's only going to be one message per array. However, Bandwidth in the future may bundle multiple callbacks. But we will not do this without telling you. You should handle these events as if they were more than one. I actually do not do that in this example, you can see I just pop the first message response or message event off that array there and grab it at that point. However, for robustness and to future-proof yourself, we really do recommend handling it as if there could be more than one message per callback even though today there's absolutely only ever going to be one. Further, if we look at the callback structure, we have the type, the description, time, and who the "To" phone number was. And then you also have this nested object called a message. This contains the ID, who the group members were, who the owner of this phone number, so this is the Bandwidth number here, and then what direction that message was. So if it was an outbound message, if we look here, we can see the direction of that message object. It's going to be out. And if it's a inbound, the direction's in. Relatively self-discoverable. However, the type also does change. You have message received for inbound, and then you have a message delivered or other event types based on the DLR, if that message failed during flight or whatnot. OK. So now we can look at this point, we have our message object here. And we have our message callback, the full kit, right here. So let's head back to our little code-along, or copy and paste along if you will. And so we need to check if it's an outbound message. If it was an outbound message, we don't really care about the status for this app, we just need to receive it. So we're going to copy some logic right here. Paste that in. Indent. And we can take a look that we're going to grab the direction of that message, that inner message, we're going to shove it to lower case, and we're going to strip out any extra spaces. More likely than not, not necessary. But I always like to be cautious when doing string compares. And then we want to check if that's out. And so we're going to have a nice little boolean here. Is DLR. If it is, then let's go ahead and just print out that log message. So we're going to say, callback message received for this ID, and here's the status. So as we receive DLRs, we're going to get those statuses print to our console. Next, let's build the "To" array. As you can see in the inbound group message, we have this "To" array here. This is all the phone numbers involved with this group conversation. So we do want to make sure that if someone wants to text this phone number as part of a group, that we respond to the whole group, and not just who sent that phone number. So if we were to look at "from" value here, if we only responded to "from," it would create a new thread on that individual's phone and it would not be part of the group anymore. So if we look at the "To" array here, we notice that this 902 is both in here, and as the owner. So we're going to extract this 902 phone number out of the "To" array, and then we're going to shove the phone number "from" back into that array there. So we're going to create a new array, take out the owner, and put it in the "from." That way, we get all of the group members involved when we respond to that message. So if we take a look at that, it's relatively trivial with Ruby, but we'll paste that in here. So we're going to grab the owner, that's why I said that's going to be the Bandwidth phone number. We're going to create a copy of the "To" array so we don't mutate the one that's there. We're going to delete the owner from that array, and then we're going to push the phone number back onto it. Then we're going to create our message requests. So most of the message that we're going to respond is the same, right? The "To" number is the same, the owner's the same, and the application ID is going to be the same. It's the text and media that's going to differentiate between the two. So let's go ahead and paste this in here. So here's our message request. Just starting. This is going to be from the Bandwidth Messaging. Inclusion there. That's why we were able to get that message request there. So we've got that going. If we look next, we need to check if it's a dog. So we'll do the same thing that we did before for the direction. We're going to grab that message text, we're going to shove it to lower case, and we're going to strip off any spaces. This will allow us to accept any variations of just the word "dog" without if you put five spaces in front or at the back, we're going to still hit on that. Then we're going to run a little switch statement based on that message text. If it's a dog, we're going to reply with a dog emoji and we're going to send this dog.jpg picture here. Otherwise, we're just going to say, "Hello from bandwidth." Let's keep going down here. So we now need to go ahead and just build the client, finish off that response, and then log the result. So we now have our messaging client pulled from our Bandwidth Client. We've initialized that earlier up there. We're going to send the account ID along with that message, and then body is going to be that message request that we built up there. We're then going to log that out to screen with whatever the response ID was. So we're going to say, hey, excuse me, we sent this message, here's the ID. And then hopefully, sooner than later, we'll receive a callback for that same message to this endpoint. So if I go through and go ahead and save this and head back to my terminal and kill that, rerun my app, I-- spoiler, you'll see some of the stuff over here. I already have this phone number wired up to my ngrok. So if I say "Yo," we can see "Hello from bandwidth." If we look at our log here, we sent the message with ID HMQ here. And then we can see here's our callback, received for this message. And the status was OK. That means message went out without a hitch. So if we send the word "dog," you can see the message ID was sent there. There's the dog emoji and the dog picture. Well, super puppy over there. Our callback was received, and then the message was delivered for carrier. It's important to note that for MMS today, we do not have super rich DLRs. Each DLR only is able to let you know that the carrier received the message, not that it got any further down the pipeline. So we're unable to provide super insight into DLRs for MMS so that anything that's a group message or contains a media would be considered a MMS. However, for regular normal SMS, we do provide a little bit extra insight through the status there. OK. So at this point, we have a relatively simple application that is deserializing the request to a string or hash, sorry. We are deserializing that or casting it to a callback message type within Ruby. We're going through checking the direction. If it's an outbound message, we're just going to log to screen and move on with our lives. Otherwise, we're going to grab the owner, we're going to build that "To" array based on group messaging, right? Just if as one person, it still works the same as it worked for groups. We're going to go ahead and build that message request to send the Bandwidth. We're going to switch on that inbound message content. If it's "dog," we're going to build that request to include a dog emoji and some media. Otherwise, we're just going to say "Hello from bandwidth." We're going to go ahead and initialize the messaging client from the Bandwidth Client. We're going to build that response, or we're going to grab the response from creating that message and log it to screen. So relatively simple application demonstrating just the basics of where everything's organized within Bandwidth and what the Ruby SDK can do for messaging. OK. So all together, right there is a final snapshot of what the method there looks like. So let's handle voice callbacks. So a little bit different than messaging, voice allows you to have different callbacks URLs for different events. You can specify as generically or as specifically as you need. So the first thing we're going to do is respond to an inbound call. We're always going to respond to the same thing, to the inbound call. We're going to ask them to play a little arithmetic game. And then as part of that, we're going to respond with a gather verb. I'll show you what that looks like in the documentation. And a gather verb, essentially, is asking for input. Please press the digit. Press 1 for pizza, 2 for tacos. So when you call this phone number, you can kind of see what we're going to do, we're going to say, "Let's, play a game." We're going to ask the user what 9 plus 2 is. So we're demonstrating what SpeakSentence does. How to speak a sentence on a call using the text to speech engine. We're going to demonstrate how to nest that SpeakSentence and that gather request. So if a user already knows the answer, they don't have to wait for the sentence to complete, they can barge through. And then we're going to grab that event, when they push the digits on their thing, and we're going to react based on their input. So we do have a relatively simple response or object method here. I'm just going to tab this out. So we're going to build our sentence. The voice is going to be Kate. We're going to create a new SpeakSentence object. This is going to be then deserialized into XML for us. So we're going to say SpeakSentence new, we're going to grab the sentence as what we just said before, the voice is Kate. With the gather, we're going to say, hey, we're going to give you only the option to press two digits. So we're not going let you get it super wrong. We're not going to put in 100 or 1,000 or a million, right? You only can put in two digits. We're going to give you 10 seconds to make that first input. After 10 seconds, the timeout will fire. You get a timeout event. We're going to nest that SpeakSentence up here. It's that 9 plus 2 and the gather verb. And then we're going to say, any time we get a result, a timeout or any event, please send that event to the ngrok URL slash callbacks slash voice slash gather. So we're going to have a different path and route all together for BXML gather events. Next, we need to go ahead and build our response object. Push the gather to it. Make that BXML. Just going to log it to screen just so we can see what's going on. And then we do need to return that BXML back to Bandwidth as part of a synchronous request so that they speak that sentence, ask the user for input on the call, and then send that event to the gather spot here. So I'm just going to kind of show what that looks like even though we haven't actually defined our gather route. So I need to bounce back over here, rerun our app. And I'm going to click the call button here. And I'm going to dial 55. And we can see that we received the callback to that end point. We can actually look at it here. We got a 404 not found, which is what we expect. But let's look at our BXML. So right here. It's tough to see, but let's just paste here. View. Turn on word wrap. That doesn't help a ton, does it? Break this out. There we go. So you see our gather is asking us to send the events there. Max digit's 2. Timeout of 10 seconds. There's our SpeakSentence wrapped within there. And there's our response. So we're going to actually need to handle that gather end point. So let's go ahead, build that out. Paste this in here. So now we've actually got a path and route set up to receive those gather events. We actually need to extract the digits from that. So unlike the original inbound call when we didn't really care what the event was because we were always doing the same thing every single time, we do have an option to make. If they got it wrong, we don't want to play the congratulatory sound, we want to play this sad trombone sound. So let's go ahead and just paste this. Oop. Let's try that again. This real quick bit of code here that we're going to parse out that JSON using the JSON parser, and we're going to assign that to data. One thing to note is that we are working to get nice objects like we had up here for the callback message from hash. In the Ruby SDK for voice events we don't have them yet. Hoping to get those sometime in this quarter if not the next. Now we need to check that current digit value. But before we do that, we've got a couple just constants to care about. We've got our wav file for success loaded here, our fail file loaded in there, and then we're just going to check real quick. Are the digits equal to 11? If so, our audio URI is going to be that success file. And if not, or anything else, it's going to be the fail file. So if you dialed 55, you're going to get the fail wav, if you dial 11, or one one, you're going to get the tada wav. Let's go ahead and build and respond with that BXML. Paste that in there tab it out. So we're going to create a new play audio verb. The URL that it takes will be this audio URI. We're going to actually hang up the call whenever the audio is done playing. We don't need to keep it around, we don't need to do any other events. Once we've played the success file or fail file, we're just going to hang up the call. So we're going to create a new hangup verb and then we're going to push, in order, the play audio verb and then the hangup verb. Finally, we're going to generate that BXML, print it to screen, and then return that back to the Bandwidth server. So let's save that. Go back to our app. Rerun it. And let's go back over here and call this number. So I'll dial 11. Get the success. And the call ends. Let's do it again. Let's dial 55. You get the sad trombone sound. But we're also going to go through. And let's look at the ngrok debug web interface just to kind of show a little bit easier what was sent and received. So if we look here on inbound call, this is what the inbound call object looked like. We have our event type initiate, which makes sense. That's our voice initiated callback URL. You can see this is the response that we responded with. And I'll print it to screen or to the console and then pore it out a little bit more. We can see, and our gather event here. Right? It got posted to the gather path that we defined. You can see that the call ID is still there. And there's our digits. It was 55, and we responded with this BXML right here. Response was the fail file. And then for the other one, for the event, that was 11 or one one, we responded with the success file. So again, everything is going to be located for you here on the Bandwidth slash examples web page, specifically under Ruby and webinar example. We've got some pre-reqs here, what environment variables you need, and what path to set up. A very quick commands here to get up and running if you want to just run the app as is, sending an app.rb right there. And other than that, it's a pretty straightforward application. We've done some in all the other programming language. So look on the website there if Ruby is not for you and you're more curious about how this works in node, there's a webinar out there for you too. So thank you for your time today, and--