Phone number verification via Twilio SMS in Meteor

lovetap.org, my current side-project, sends people pictures from their Google photo albums directly to their phone at random intervals.
I wanted to make sure that people who sign up for the service actually own the phone number they're signing up with. I found a lot of tutorials on how to send a random code to a user's phone that they then have to enter on the website in order to verify their phone, but I was looking for something even simpler. lovetap will send you a text and all you have to do is reply "Yes" to verify your phone number.
How do you implement something like this with Twilio in a Meteor application? Well, it's actually not that hard:
First, you'll need a twilio account (duh).
When a user enters a number that hasn't been verified, send a text message asking them to verify (this is server-side code):
HTTP.post(twilioBaseUrl + '/Accounts/' + twilioAccountSid + '/Messages', {
auth: twilioAccountSid + ':' + twilioAuthToken,
params: {
From: twilioFromNumber,
To: toNumber,
Body: body
}
});
Now, this is where it gets a little more complicated. The next step is to mark the number as verified when a user replies "Yes" to the message we just sent him. To achieve this, we'll tell twilio to hit a web hook in our application whenever someone responds to a text message.
In your twilio account, go to "Dev Tools", then "TwiML Apps". Enter a friendly name, and under "SMS & MMS", the URL of the webhook in your application. This is the URL that twilio will get whenever one of your users replies to a text message you sent them. I set it to "GET" as I couldn't get it to work with a POST. In the Meteor app, we'll have to create a new route in iron router for our web hook:
this.route('/webhooks/twilio/handleresponse', {
where: 'server'
})
.get(function() {
this.response.end(Twilio.handleResponse(this));
});
Now, we just need to handle the data that twilio sends in the request:
// extract data from twilio-post
var date = new Date();
var messageSid = routeContext.params.query.MessageSid;
var accountSid = routeContext.params.query.AccountSid;
var from = routeContext.params.query.From;
var to = routeContext.params.query.To;
var body = routeContext.params.query.Body;
if (!from){
return 'No from-number';
}
// unfortunately, iron router loses the + because it's URL encoded.
// we'll clumsily add it by hand:
from = '+' + from.trim();
to = '+' + to.trim();
// validated or not?
var verified = false;
body = body.toLowerCase();
if (body.indexOf('yes') !== -1 || body === 'y') {
verified = true;
}
// update validated numbers collection
var responseBody = '';
if (verified) {
// set number to verified:
NumberStatus.update({
clean_number: from
}, {
'$set': {
verified_on: date
}
}, {
multi: true
});
responseBody = 'Thanks! You\'ll soon receive your first picture!';
} else {
// set number to not verified:
NumberStatus.update({
clean_number: from
}, {
'$unset': {
verified_on: date
}
}, {
multi: true
});
responseBody = 'OK, we won\'t send you pictures.\nIf you change your mind, simply reply "Yes" to this message.';
}
// Set the headers
routeContext.response.writeHead(200, {
'Content-Type': 'application/xml'
});
// respond with twiml:
return '<?xml version="1.0" encoding="UTF-8"?><Response><Sms from="[TWILIOFROM]" to="[TO]">[BODY]</Sms></Response>'
.replace('[TWILIOFROM]', twilioFromNumber)
.replace('[TO]', from)
.replace('[BODY]', responseBody);
We now marked the number as verified in our database and can safely send to it.