I'm trying to set up messages delivery status check with twilio. For some reasons twilio statusCallback doesn't fire. Could you please help me to find an error?
Here is a file where I do initialization and send messages:
const Twilio = require('twilio');
const {
TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN,
TWILIO_PHONE_NUMBER
} = require('config');
const client = new Twilio(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);
module.exports = {
send: (body, phoneNumber) => client.messages.create({
body,
to: phoneNumber,
from: TWILIO_PHONE_NUMBER,
statusCallback: 'http://postb.in/b/XXXXXXXX'
})
.then((message) => {
console.log(message.sid);
return message.sid;
})
};
Here is a test where I call message sending after initialization:
const { expect } = require('chai');
const smsUtility = require('utils/sms');
describe('Sms utility', () => {
it('should send a message and return message sid', (done) => {
const body = 'Body';
const number = '+XXXXXXXXXXX';
smsUtility.send(body, number)
.then((messageSid) => {
expect(typeof messageSid).to.equal('string');
return done();
})
.catch((err) => {
console.log(err);
return done();
});
});
});
I use test credentials, but when I replace it with real twilio credentials, I successfully receive a message, so this part works fine. Also, when I try to ping postb.in url manually (with curl), it also works OK. Only statusCallback doesn't work.
Thanks.
Twilio developer evangelist here.
It looks to me like you have everything set up nicely aside from your Postbin URL.
I noticed you show your URL as http://postb.in/b/XXXXXXXX. But the /b/ version of the URL is the dashboard for your Postbin. Requests to the dashboard won't show up on the dashboard.
Instead, you should use the URL that looks like: http://postb.in/XXXXXXXX. Try that and let me know if it's working.
Related
Is it possible to programmatically register multiple webhook URLs for a single Twilio number? (When we receive an inbound SMS we would like multiple webhooks to be called with that data)
The docs for "update an incomingPhoneNumber resource" suggest that you can optionally set the smsUrl, but it's not clear how you would set multiple webhook URLs for a single number.
Not through the Twilio console options. You can write a Twilio Function that can fork out multiple outbound webhooks such as below, and then you just point the Twilio console to this Twilio Function.
const axios = require('axios');
const qs = require('querystring');
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.MessagingResponse();
let {
ApiVersion,
SmsSid,
SmsStatus,
SmsMessageSid,
NumSegments,
ToState,
From,
MessageSid,
AccountSid,
ToCity,
FromCountry,
ToZip,
FromCity,
To,
FromZip,
ToCountry,
Body,
NumMedia,
FromState
} = event;
let requestBody = {
ApiVersion,
SmsSid,
SmsStatus,
SmsMessageSid,
NumSegments,
ToState,
From,
MessageSid,
AccountSid,
ToCity,
FromCountry,
ToZip,
FromCity,
To,
FromZip,
ToCountry,
Body,
NumMedia,
FromState
};
let url1 = "https://example.com/1";
let url2 = "https://example.com/2";
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
Promise.all([
axios.post(url1, qs.stringify(requestBody), config),
axios.post(url2, qs.stringify(requestBody), config)
]).then(result => {
callback(null, twiml);
}).catch(err => {
console.log(err);
callback(err);
});
};
I want to know the current callers(client) Phone Number and then be able to verify it from my HubSpot CRM to see which language my client speaks. Then Enqueue the call to the agent that speaks the language or give the Caller an option to choose the preferred language.
Anyhoo
Right now my goal is to just get the number of the caller(client).
exports.handler = function(context, event, callback) {
console.log(event);
//const request = require('request');
//const client = require('twilio');
const res = new Twilio.Response();
res.appendHeader('Access-Control-Allow-Origin', '*');
res.appendHeader('Access-Control-Allow-Methods', 'OPTIONS POST');
res.appendHeader('Content-Type', 'application/json');
res.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
const fetch = require("node-fetch");
var phoneNumber = event.From; //|| [ "+63XXXXXXXXXX" ];
const apiUrl = "https://myapiurl.com/contact-details.php?number="+phoneNumber;
fetch(apiUrl,{
method: 'GET',
headers: { 'Content-Type': 'application/x-www-form-urlencoded'}
})
.then( response =>{
return response.json();
})
.then(json=>{
console.log(json);
res.setBody(json);
callback(null,res);
})
.catch(error=>{
console.log(error);
callback();
});
};
Also let me know if there is a better way to do it.
Twilio developer evangelist here.
Your code shows you getting event.From. This is the number of the person calling, so you have already done it!
I am trying to develop a simple application with Twilio and I have some issues. I have the code made with Python to make the call.
def makeCall(self, phoneNumber, from_num, message):
call = self.client.calls.create(
method='POST',
machine_detection='Enable',
to= phoneNumber,
from_= from_num,
url= self.url + urlencode({'Message' : message}),
status_callback= self.url_callback,
status_callback_method = 'POST'
)
return call.sid
And I also have a Node.js application with the Twiml response.
const express = require('express');
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const urlencoded = require('body-parser').urlencoded;
const app = express();
app.post('/start', (request, response) => {
const twiml = new VoiceResponse();
const gather = twiml.gather({
numDigits: 1,
action: '/confirmation'
});
gather.say({
language:'en-EN'
}, 'Hello, are you ok?' );
gather.pause({
length: 1
});
gather.say({
language:'en-EN'
}, 'If no press one, if yes press two');
response.type('text/xml');
response.send(twiml.toString());
});
app.post('/confirmation', (request, response) => {
const twiml = new VoiceResponse();
if (request.body.Digits) {
switch (request.body.Digits) {
case '2':
twiml.say({
language:'en-EN'
}, 'I am sorry to hear that');
case '1':
twiml.say({
language:'en-EN'
}, 'Perfect!');
default:
twiml.say({
language:'en-EN'
}, 'Sorry, I don't understand you.');
twiml.pause({
length: 1
});
twiml.say({
language: 'en-EN'
}, 'Repeat please');
}
}
response.type('text/xml');
response.send(twiml.toString());
});
app.post('/callback', (request, response) => {
console.log(request.body.CallStatus);
console.log('--------------------');
console.log(request.body.AnsweredBy);
console.log('--------------------');
response.type('text/xml')
response.send(request.AnsweredBy)
});
app.listen(3000);
The problem is that when I execute the python function. If the user reject the call or doesn't answer, it sends a voicemessage to the answering machine and I would like to avoid it. I would also like to detect in the python code, if the call is rejected or not answered.
Thanks in advance
Twilio developer evangelist here.
You can't detect whether the call was answered in your python code that creates the call. That will queue up the call to be dispatched by Twilio, so all further events will happen asynchronously to that API call.
For your Node.js application that is receiving the webhook you can check what the current status of the call is by inspecting the CallStatus parameter that is sent as part of the body of the request. The CallStatus can be one of: "queued", "ringing", "in-progress", "completed", "busy", "failed" or "no-answer" and you can see more about the CallStatus parameter in the documentation.
To read the CallStatus parameter, you'll need to ensure you are using the body-parser middleware properly, urlencoded is a function and you need to set the express app to use it.
const urlencoded = require('body-parser').urlencoded;
const app = express();
app.use(urlencoded({ extended: false });
You can then get the call status in your response function.
app.post('/start', (request, response) => {
console.log(request.body.CallStatus);
// and so on
Then you can handle it from there.
I have created a Twilio function that I would like to use to send my affiliate referral link to subscribers of an application that come through my channel.
It works fine with a static to / from number, however I would like to make the "to" field a dynamic variable that can be manipulated via a HTTP/Webhook POST when a Zapier detects a new subscriber to my Mailchimp mailing list and pass their phone number as the variable.
I am also unclear what I need to do to authenticate the client (Zapier) that is making the POST as I do not want the function open to the world to use, if any insights can be shared on this it would be sincerely appreciated - I am a very inexperienced programmer trying to learn very quickly!
#philnash - thanks for your suggestion, implementing it slowly!
Many thanks in advance!
exports.handler = function(context, event, callback) {
const appCodes = ['code1', 'code2', 'code3', 'code4']
var smsBody = refCode ();
function refCode () {
return appCodes[Math.floor((Math.random() * appCodes.length))];
};
context.getTwilioClient().messages.create({
to: '+11112223333', // How do I make this dynamic from HTTP/Zapier Webhook POST???
from: '+1444555666',
body: `Get the App: ${smsBody}`
}).then(msg => {
callback(null, msg.sid);
}).catch(err => callback(err));
}
Twilio developer evangelist here.
I presume the Zapier webhook is sending the details, including the phone number, as the body of the POST request.
All the parameters in a request body appear on the event object that is passed into your handler. You probably want to run a test where you print out the contents of the event object to see what you are being passed. You can do this with:
exports.handler = function(context, event, callback) {
for (let key in event) {
console.log(`${key}: ${event[key]}`);
}
// ... rest of the function
}
Then, when you figure out what parameter is storing the number, you can use that in the call to create the message.
Let me know if that helps at all.
Try this:
exports.handler = function(context, event, callback) {
for (let key in event) {
console.log(`${key}: ${event[key]}`);
}
// ... rest of the function
callback(null, 'complete');
};
Thanks everyone for your input, it was sincerely appreciated! I was able to solve this with the following code:
exports.handler = function(context, event, callback) {
const appCodes = ['code1', 'code2', 'code3', 'code4']
var smsBody = refCode ();
var subNum = event.primaryPhone || 'There is no subscriber number'; // primaryPhone sent via HTTP post to twilio function
function refCode () {
return appCodes[Math.floor((Math.random() * appCodes.length))];
};
context.getTwilioClient().messages.create({
to: `${subNum}`, // parameters & values recieved from HTTP POST are available within the twilio functions "event" context
from: '+1444555666',
body: `Get the App: ${smsBody}`
}).then(msg => {
callback(null, msg.sid);
}).catch(err => callback(err));
}
I've read here that it's possible to send an IPN directly to a Google cloud function. I have my Google Cloud functions running on Firebase on an index.js file.
I've set up my Paypal buttons to send the IPN to a page on my webapp.
Here is an example of one of the functions I'm running off Google Cloud Functions/Firebase:
// UPDATE ROOMS INS/OUTS
exports.updateRoomIns = functions.database.ref('/doors/{MACaddress}').onWrite((change, context) => {
const beforeData = change.before.val();
const afterData = change.after.val();
const roomPushKey = afterData.inRoom;
const insbefore = beforeData.ins;
const insafter = afterData.ins;
if ((insbefore === null || insbefore === undefined) && (insafter === null || insafter === undefined) || insbefore === insafter) {
return 0;
} else {
const updates = {};
Object.keys(insafter).forEach(key => {
updates['/rooms/' + roomPushKey + '/ins/' + key] = true;
});
return admin.database().ref().update(updates); // do the update}
}
return 0;
});
Now question:
1) I want to add another function to process IPN from Paypal as soon as I have a transaction. How would I go about this?
I'll mark the answer as correct if solves this first question.
2) how would that Google cloud function even look like?
I'll create another question if you can solve this one.
Note I am using Firebase (no other databases nor PHP).
IPN is simply a server that tries to reach a given endpoint.
First, you have to make sure that your firebase plan supports 3rd party requests (it's unavailable in the free plan).
After that, you need to make an http endpoint, like so:
exports.ipn = functions.http.onRequest((req, res) => {
// req and res are instances of req and res of Express.js
// You can validate the request and update your database accordingly.
});
It will be available in https://www.YOUR-FIREBASE-DOMAIN.com/ipn
Based on #Eliya Cohen answer:
on your firebase functions create a function such as:
exports.ipn = functions.https.onRequest((req, res) => {
var reqBody = req.body;
console.log(reqBody);
// do something else with the req.body i.e: updating a firebase node with some of that info
res.sendStatus(200);
});
When you deploy your functions go to your firebase console project and check your functions. You should have something like this:
Copy that url, go to paypal, edit the button that's triggering the purchase, scroll down to Step 3 and at the bottom type:
notify_url= paste that url here
Save changes.
You can now test your button and check the req.body on your firebase cloud functions Log tab.
Thanks to the answers here, and especially to this gist: https://gist.github.com/dsternlicht/fdef0c57f2f2561f2c6c477f81fa348e,
.. finally worked out a solution to verify the IPN request in a cloud func:
let CONFIRM_URL_SANDBOX = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
exports.ipn = functions.https.onRequest((req, res) => {
let body = req.body;
logr.debug('body: ' + StringUtil.toStr(body));
let postreq = 'cmd=_notify-validate';
// Iterate the original request payload object
// and prepend its keys and values to the post string
Object.keys(body).map((key) => {
postreq = `${postreq}&${key}=${body[key]}`;
return key;
});
let request = require('request');
let options = {
method: 'POST',
uri : CONFIRM_URL_SANDBOX,
headers: {
'Content-Length': postreq.length,
},
encoding: 'utf-8',
body: postreq
};
res.sendStatus(200);
return new Promise((resolve, reject) => {
// Make a post request to PayPal
return request(options, (error, response, resBody) => {
if (error || response.statusCode !== 200) {
reject(new Error(error));
return;
}
let bodyResult = resBody.substring(0, 8);
logr.debug('bodyResult: ' + bodyResult);
// Validate the response from PayPal and resolve / reject the promise.
if (resBody.substring(0, 8) === 'VERIFIED') {
return resolve(true);
} else if (resBody.substring(0, 7) === 'INVALID') {
return reject(new Error('IPN Message is invalid.'));
} else {
return reject(new Error('Unexpected response body.'));
}
});
});
});
Also thanks to:
https://developer.paypal.com/docs/classic/ipn/ht-ipn/#do-it
IPN listener request-response flow: https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNImplementation/
To receive IPN message data from PayPal, your listener must follow this request-response flow:
Your listener listens for the HTTPS POST IPN messages that PayPal sends with each event.
After receiving the IPN message from PayPal, your listener returns an empty HTTP 200 response to PayPal. Otherwise, PayPal resends the IPN message.
Your listener sends the complete message back to PayPal using HTTPS POST.
Prefix the returned message with the cmd=_notify-validate variable, but do not change the message fields, the order of the fields, or the character encoding from the original message.
Extremely late to the party but for anyone still looking for this, PayPal have made a sample in their JS folder on their IPN samples Github repo.
You can find this at:
https://github.com/paypal/ipn-code-samples/blob/master/javascript/googlecloudfunctions.js