Skip to content

Instantly share code, notes, and snippets.

@zechdc
Created July 25, 2021 15:00
Show Gist options
  • Save zechdc/5208a43200af4d9f863a9e8418b73c8a to your computer and use it in GitHub Desktop.
Save zechdc/5208a43200af4d9f863a9e8418b73c8a to your computer and use it in GitHub Desktop.
Advanced Twilio Setup Guide for Textable.io - Allow you to use Twilio Functions and Twilio Studio
// https://www.textable.co/docs/twilio-setup-guide/
// Textable setup guide doesn't allow you to use Twilio functions or Studio easily.
// Instead, you can create a twilio function with this code, then use that function
// in Twilio Studio
// Add axios 0.20.0 as a dependency under Functions Global Config, Dependencies
const axios = require('axios');
// Add qs 6.9.4 as a dependency under Functions Global Config, Dependencies
const qs = require('qs');
const crypto = require('crypto');
// Add awesome-phonenumber 2.55 as a dependency under Functions Global Config, Dependencies
const PhoneNumber = require( 'awesome-phonenumber' );
exports.handler = async function(context, event, callback) {
await postToTextable(context, event);
await postToSlack(event);
return callback(null);
};
async function postToTextable(context, event) {
const textableUrl = 'https://inbound.textable.app/receive?provider=twilio';
const twilioRequestSignature = getExpectedTwilioSignature(context.AUTH_TOKEN, textableUrl, event);
await axios({
method: 'post',
url: textableUrl,
data: qs.stringify(event),
headers: {
'X-Twilio-Signature': twilioRequestSignature,
'User-Agent': 'TwilioProxy/1.1',
'Content-Type': 'application/x-www-form-urlencoded',
},
})
}
async function postToSlack(event) {
const message = event.Body;
const mediaCount = event.NumMedia;
const fromCity = event.FromCity;
const fromNumber = event.From;
const phoneNumber = new PhoneNumber(fromNumber);
const fromState = event.FromState;
console.log(JSON.stringify(event));
const blocks = [];
if (message) {
blocks.push(SlackBlockKit.section.plain_text(message));
}
blocks.push(SlackBlockKit.context.plain_text(`From: ${phoneNumber.getNumber('national')} - ${fromCity}, ${fromState} - ${mediaCount} media/attachments`));
blocks.push(SlackBlockKit.divider())
await axios({
method: 'post',
url: YOUR_SLACK_WEBHOOK_URL,
data: {
"blocks": blocks
}
})
}
const SlackBlockKit = {
divider: () => {
return {
"type": "divider"
}
},
context: {
plain_text: (text) => {
return {
"type": "context",
"elements": [
{
"type": "plain_text",
"text": text,
"emoji": true
}
]
}
},
},
section: {
plain_text: (text) => {
return {
"type": "section",
"text": {
"type": "plain_text",
"text": text,
"emoji": true
}
}
},
},
image: {
title: (url, title) => {
return {
"type": "image",
"title": {
"type": "plain_text",
"text": title,
"emoji": true
},
"image_url": url,
"alt_text": title
}
},
},
};
/**
Unfortunately, the twilio node library doesn't expose this method. So I copy/pasted it
Textable will reject any requests that don't have a proper signature.
Copy pasted from here: https://github.com/twilio/twilio-node/blob/main/lib/webhooks/webhooks.js#L61-L74
Here are a couple articles about how the signature is used in wehooks:
- https://www.twilio.com/docs/usage/security
- https://www.twilio.com/docs/usage/webhooks/webhooks-security
Utility function to get the expected signature for a given request
@param {string} authToken - The auth token, as seen in the Twilio portal
@param {string} url - The full URL (with query string) you configured to handle this request
@param {object} params - the parameters sent with this request
@returns {string} - signature
*/
function getExpectedTwilioSignature(authToken, url, params) {
if (url.indexOf('bodySHA256') !== -1 && params === null) {
params = {};
}
var data = Object.keys(params)
.sort()
.reduce((acc, key) => acc + key + params[key], url);
return crypto
.createHmac('sha1', authToken)
.update(Buffer.from(data, 'utf-8'))
.digest('base64');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment