Need help formatting a POST request

First a little background. I have several projects that need to send SMS texts for various notifications. I use a service called Twilio to actually send those SMS messages. I was (and still am) unable to figure out how to format a POST request to fire off directly to Twilio, so I use an intermediate service from Script.io

I need to eliminate the use of Scriptr.io and issue these Post requests directly to Twilio. The code (json?) to fire these requests in Scriptr looks like:

	var callResult = http.request({
    	"url" : "https://api.twilio.com/2010-04-01/Accounts/xxx...mytoken...xxx/Messages.json",
    	"method": "POST",
    	"params": {"From": "2055551212", "To": “6095551212”, "Body": “Text I want to send”},
    	"authCreds": ["xxx...mytoken...xxx "]
	});

[NOTE: I’ve simplified some things – the phone numbers, text, etc are actually done using some variables – I’ve hard-coded the above just to make it clearer what this is doing.]

My question is how to do this directly from Arduinos (MKR1000, etc).

I’m able to send a POST request to Scriptr from Arduino using the following code. What I don’t know how to do is format the above POST request to get it to work correctly as part of the client.print call below.

// Error checking and retry logic removed for clarity
const char* bearer   = "xxx...scriptr token...xxx";
WiFi.begin(ssid, password);  
POSTtoScriptr (“Text I want to send”)

...

int POSTtoScriptr (String msg) {
  if (!client.connect("api.scriptrapps.io", 443)) {
    return false;
  }
  client.print(String("POST ") + "/SMS/?sms=" + msg + " HTTP/1.1\r\n" +
               "Host: api.scriptrapps.io\r\n" + 
               "Authorization:bearer " + bearer + "\r\n" + // 23 + 68
               "Content-type: application/x-www-form-urlencoded\r\n" +
               "Connection: close\r\n\r\n");
  unsigned long timeout = millis();
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
      client.stop();
      return false;
    }
    yield();
  }
  while(client.available()){
    String line = client.readStringUntil('\r');
    yield();
  }
  return true;
}  

Any help would be appreciated. Thanks.

To send an SMS directly from an Arduino (e.g., MKR1000) to Twilio without using Scriptr.io, you need to format the HTTP POST request correctly. The key steps involve setting up the HTTP headers, including the Authorization header for authentication, and the body of the request with the message details. Here's how you can adapt your existing code to send a POST request directly to Twilio:

  1. Establish a WiFi connection: Ensure your Arduino is connected to the internet.
  2. Prepare the HTTP POST request: This includes setting the correct URL, headers, and body content.
  3. Send the request: Use the client.print method to send the request to Twilio's API.

Here's an example code snippet based on your requirements:

#include <SPI.h>
#include <WiFiNINA.h>

// Replace with your network credentials
const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";

// Twilio credentials
const char* accountSid = "your_TWILIO_ACCOUNT_SID";
const char* authToken = "your_TWILIO_AUTH_TOKEN";
const char* fromNumber = "your_TWILIO_PHONE_NUMBER";
const char* toNumber = "RECIPIENT_PHONE_NUMBER";

// The message you want to send
const char* messageBody = "Text I want to send";

// Initialize the WiFi client library
WiFiClient client;

void setup() {
 // Start the serial communication
 Serial.begin(9600);

 // Connect to Wi-Fi
 WiFi.begin(ssid, password);
 while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
 }
 Serial.println("Connected to WiFi");

 // Send the POST request to Twilio
 sendSMS(toNumber, messageBody);
}

void loop() {
 // Nothing to do here
}

void sendSMS(const char* to, const char* body) {
 if (!client.connect("api.twilio.com", 443)) {
    Serial.println("Connection to Twilio failed");
    return;
 }

 // Prepare the HTTP POST request
 String authString = "Basic " + String(accountSid) + ":" + String(authToken);
 String postData = "From=" + String(fromNumber) + "&To=" + String(to) + "&Body=" + String(body);
 int postDataLength = postData.length();

 // Send the request
 client.println("POST /2010-04-01/Accounts/" + String(accountSid) + "/Messages.json HTTP/1.1");
 client.println("Host: api.twilio.com");
 client.println("Authorization: " + authString);
 client.println("Content-Type: application/x-www-form-urlencoded");
 client.print("Content-Length: ");
 client.println(postDataLength);
 client.println();
 client.println(postData);

 // Wait for the response
 while (client.connected()) {
    if (client.available()) {
      String line = client.readStringUntil('\r');
      Serial.print(line);
    }
 }

 client.stop();
}

This code snippet demonstrates how to send an SMS directly from an Arduino to Twilio. Make sure to replace placeholders like your_SSID, your_PASSWORD, your_TWILIO_ACCOUNT_SID, your_TWILIO_AUTH_TOKEN, your_TWILIO_PHONE_NUMBER, and RECIPIENT_PHONE_NUMBER with your actual credentials and phone numbers.

Remember, the authString is constructed by encoding your Twilio Account SID and Auth Token in Base64 format. This is a crucial step for authentication with Twilio's API. The postData string contains the message details, including the sender's number (From), the recipient's number (To), and the message body (Body). The Content-Length header is dynamically set to the length of the postData string.

This approach eliminates the need for Scriptr.io and allows you to send SMS messages directly from your Arduino to Twilio.

The encoding for Basic auth bakes in the colon, effectively
Basic base64(name:password)
not
Basic base64(name):base64(password)
as implied by the code above for authString

The postData for a body of type application/x-www-form-urlencoded must encode spaces and many "reserved" punctuation characters, like = and ?

Thank you both for taking the time to review and respond to this.

I'm a little confused about the encoding of the colon for the authString. When Scriptr was issuing the POST to Twilio, it had:

"authCreds": ["Encoded_Account_SID","Encoded_Account_Token"]

With the comma outside the encoded information. Wouldn't this mean that the colon is not to be encoded, or does sending as json change that?

Also, do I need to append \r\n or does the .println take care of that?

And thanks for pointing out that many punctuation characters need to be encoded - I was aware of that but anyone reading this in the future needs to know.

SIDE NOTE FOR ANYONE LOOKING AT THIS IN THE FUTURE:

In my attempt to simplify the original code, I mis-typed in my original post, The corrected lines should be:

"url" : "https://api.twilio.com/2010-04-01/Accounts/xxx...mySID...xxx/Messages.json",

and

"authCreds": ["xxx...mySID...xxx","xxx...mytoken...xxx "]

No colon in the resulting header value over the wire. For example

$ curl https://httpbin.org/headers -u MyName:MyPassword
{
  "headers": {
    "Accept": "*/*", 
    "Authorization": "Basic TXlOYW1lOk15UGFzc3dvcmQ=", 

and the reverse

$ echo -n "TXlOYW1lOk15UGFzc3dvcmQ=" | base64 --decode
MyName:MyPassword

And with no password

$ curl https://httpbin.org/headers -u MyName # no password
Enter host password for user 'MyName':
{
  "headers": {
    "Accept": "*/*", 
    "Authorization": "Basic TXlOYW1lOg==", 
$ echo -n "TXlOYW1lOg==" | base64 --decode
MyName:

Not being familiar with neither Scriptr nor Twilio, it's pretty clear Scriptr is expecting two strings in an array for the authCreds in this case, and the comma just separates those in the JSON/JavaScript. And then presumably it does the work of concatenating them with the colon and encoding.

.println adds one \r\n. When you're printing one line at a time, use it unless you need to do printf instead. For example, these two are equivalent

client.printf("Content-Length: %d\r\n", postDataLength);

client.print("Content-Length: ");
client.println(postDataLength);

You could get reasonable C++ programmers to agree on the pros and cons of each, but not necessarily on their relative weight.

I keep getting 400 errors. What gets sent is:

POST /2010-04-01/Accounts/AC1b2390c4c50cf4b24670fb88e3fe9a33/Messages.json HTTP/1.1
Host: https://api.twilio.com
Authorization: Basic QU...Base64 Account SID:Token...==
Content-Type: application/x-www-form-urlencoded
Connection: close
Body=Yourmessagehere&From=2055551212&To=6095551212

I've tried just the Token too. The auth string is SID:Token all encoded as a single string.

I must be missing something simple, but I can't find it.

Also, I originally had api.twilio.com and the response indicated that it was transferring to HTTPS so I prefixed the API URL with https:// and the warning went away.

is just for the host name. No protocol nor port. Those are in the URL because that's "where the message should go and how is it sent" versus the content of the message once you get there. This header is required because a web server can at given IP address and port can host multiple web sites. Should be

Host: api.twilio.com

I'd omit the Connection header. Most importantly there must be a blank line between the headers and the body.

If everything else is good but the auth is incorrect (while formed correctly), you should get 401 or 403 instead of 400. For a 400, the server may have more details in its response body.

It fails the same (400 Bad Request) with or without the https:// in Host: api.twilio.com

But when the https:// prefix isn't there, I also get

  400 The plain HTTP request was sent to HTTPS port

I removed the Connection header line and also added a blank line before Body=..... and still get the 400 error. There must be something stupid I'm missing but I sure can't see it.

Removing the prefix was actually progress. With it there, the Host header value is malformed, and apparently there is no specific message for that -- maybe because how can you screw up something so basic :slight_smile:

With that fixed, you now have an actual "legitimate" error, and it's exactly what that message says. You are connecting on the default HTTPS port, 443. But you are using the plain WiFi/HTTP client, not the secure one. So replace

  WiFiClient client;

with

  WiFiClientSecure client;
  client.setInsecure();

If the client is declared globally, you put the call to setInsecure in your function body, before you use it (just once). You need this call because you don't have the server's or root certificate, to validate the server is who they claim to be. So you'll just trust them for now, but it's something you can fix. The connection is still encrypted. (If you don't make the call, the client will refuse to connect.)

I'm running the Arduino IDE for a MKR1000 board. The libraries I'm using are:

#include <WiFi101.h>
#include <WiFiUdp.h>

When I make the change to

WiFiClientSecure client;

I get 'WiFiClientSecure' does not name a type, did you mean 'WiFiClient'?

Am I using the wrong libraries for MKR1000?

Along the same lines, I tried

WiFiSSLClient client;

with

client.connectSSL("api.twilio.com", 443))

And it doesn't connect any more.

Thanks

WiFiClientSecure is for ESP32 boards (at least). I am not familiar with the others, but looks like for WiFiSSLClient, there is no option for insecure. You need to Upload SSL Root Certificates. This is under the IDE Tools menu, but as of IDE 2.2.0, MKR 1000 is no longer supported, so you have to follow an older procedure with IDE 1.x.

Typically, a site has its relatively short-term certificate issued by an intermediary; and in turn that intermediary has its certs backed by a long-lived root certificate. You can find this info by visiting the site in a browser, clicking the lock next to the start of the URL, and then poking around the resulting UI. For example, the root for google.com is Google Trust Services "GTS Root R1"; for github.com, "USERTrust ECC Certification Authority"; and for twilio.com, "DigiCert Global Root G2". Multiple sites from different entities can share the same root, so you're not necessarily uploading a root for each site. Apparently the Firmware/Cert Updater will just go take a look and upload it if needed.

The example for WiFiSSLClient, which is also here at Github if it's not in your Examples menu, tries to connect to Google. I don't know if the root for that is already uploaded. You can try swapping with Twilio before and/or after uploading the certs to make sure that is working.

If it's a certificate issue, I can probably obtain it from Twilio. However, if it's a short term certificate does that mean that every so often I'll need to upload a new certificate into the MKR1000s?

I did run across something in the IDE version I'm using (1.8.9) under the firmware updater for adding SSL certificates.

Apparently the native Wifi support in the WiFi101 library does not support SSL very well. I found an article with a similar issue on a different API, and their solution was to use ArduinoBearSSL library.

THAT WORKED! I am now able to fire off requests to Twilio.

Thank you all for all of your help! It was definitely needed to get my POST request formatted correctly.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.