Signing requests

Signatures validate the authenticity of the person who interacts with Nexmo.

You use a signature to:

  • Verify that a request originates from a trusted source
  • Ensure that the message has not been tampered with en-route
  • Defend against interception and later replay

A signature is the MD5 hash  of:

  • The parameters - all the parameters in a request sorted in alphabetic order
  • A timestamp - a UNIX timestamp at UTC + 0 to protect against replay attacks
  • Your SIGNATURE_SECRET - the key supplied by Nexmo that you use to sign or validate requests

The signature has a leading &. All parameters in the hash input, apart from your SIGNATURE_SECRET are separated by &.

HMAC-SHA1/256/512   is also supported. Contact for more information.

Note: Using signatures is an optional improvement on using the standard api_secret. You use the SIGNATURE_SECRET instead of your api_secret in a signed request.

The following example shows a signed request to the SMS API:

The workflow for using signed messages is:

Signing requests workflow

  1. Create a signed request to send an SMS.
  2. Check the response codes and ensure that you sent the request correctly.
  3. Your message is delivered to the handset. The user's handset returns a delivery receipt.
  4. If you requested signed delivery receipts and inbound messages validate the signature.

Setting up message signing

To setup message signing:

  1. Contact and request message signing. The options are:
  • Outbound messages can be signed.
  • Outbound messages must be signed.
  • Inbound messages and DLRs sent to your webhook endpoint are signed.
  1. Nexmo supplies you with the SIGNATURE_SECRET you use to encode and decode signatures.

    Note: this is not your api_secret.

  2. Implement the message signing workflow.

Implementing the message signing workflow

To sign your messages:

  1. Create a signed request:

    var https = require('https');
    var crypto = require('crypto');
    var security_secret = 'SECURITY_SECRET';
    var security_method = 'sha256' // Possible values md5, sha1, sha256 or sha512
    var parameters = {
     api_key: 'API_KEY',
     to: '441632960960',
     from: '441632960961',
     text: 'Hello from Nexmo',
     type: 'text',
     timestamp: Math.floor(new Date() / 1000)
    //Sort the parameters
    var param_array = new Array();
    for (key in parameters) {
        param_array.push(key + '=' + parameters[key]);
    var sorted_params = param_array.sort();
    if (security_method == 'md5') {
        var signing_url = '&' + sorted_params.join('&') + security_secret ;
        var hash = crypto.createHash(security_method).update(signing_url).digest('hex');
    } else {
        var signing_url = '&' + sorted_params.join('&');
        var hash = crypto.createHmac(security_method, security_secret).update(signing_url).digest('hex');
    parameters['sig'] = hash ;
    var data = JSON.stringify(parameters );
    var options = {
     host: '',
     path: '/sms/json',
     port: 443,
     method: 'POST',
     headers: {
       'Content-Type': 'application/json',
       'Content-Length': Buffer.byteLength(data)
    var req = https.request(options);
    var responseData = '';
    req.on('response', function(res){
     res.on('data', function(chunk){
       responseData += chunk;
     res.on('end', function(){

  2. Check the response codes to ensure that you sent the request to Nexmo correctly:

    //Decode the json object you retrieved when you ran the request.
    var decodedResponse = JSON.parse(responseData);
    console.log('You sent ' + decodedResponse['message-count'] + ' messages.\n');
    decodedResponse['messages'].forEach(function(message) {
        if (message['status'] === "0") {
          console.log('Success ' + decodedResponse['message-id']);
        else {
          console.log('Error ' + decodedResponse['status']  + ' ' +  decodedResponse['error-text']);

    If you did not generate the signature correctly the status is 14, invalid signature

  3. Your message is delivered to the handset. The user's handset returns a delivery receipt.

  4. If your delivery receipts and inbound messages are signed, validate the signature:

    var http = require('http')
    var url = require('url')
    var crypto = require('crypto');
    var security_secret = 'SECURITY_SECRET';
    // create the http server
    http.createServer(function (request, response) {
      if(request.method=='POST') {
          //Do something for post request
          console.log("post message");
      else if(request.method=='GET') {
          //Turn the query string onto an object
          var url_parts = url.parse(request.url,true).query;
          if (url_parts.hasOwnProperty('sig')){
            //Compare the local time with the timestamp
            var now = Math.floor(new Date() / 1000);
            var message_timestamp = url_parts.timestamp;
            //Message cannot be more than 5 minutes old
            var max_delta = 5 * 60;
            difference = Math.abs( now - message_timestamp );
            if (difference > max_delta)
                console.log("Timestamp difference greater than 5 minutes");
            else {
                //Sort the parameters
                message_signature = url_parts.sig;
                //Remove the signature from the request parameters
                delete url_parts.sig;
                //Create the signing url using the sorted parameters and your SECURITY_SECRET
                var param_array = new Array();
                for (key in url_parts) {
                    param_array.push(key + '=' + unescape(url_parts[key]));
                var sorted_params = param_array.sort();
                var signing_url = '&' + sorted_params.join('&') + security_secret ;
                //Add your md5 hash of your parameters to your parameters
                var generated_signature = crypto.createHash('md5').update(signing_url).digest('hex');
                //A timing attack safe string comparison to validate hash
                var valid = 0;
                for (var i = 0; i < generated_signature.length; ++i) {
                    valid |= (generated_signature.charCodeAt(i) ^ message_signature.charCodeAt(i));
                if (valid == 0)
                  console.log("Message was sent by Nexmo");
                  console.log("Alert: message not sent by Nexmo!");
      //Send the 200 ok to Nexmo so you don't get sent the DLR again.
      response.writeHead(200, {"Content-Type": "text/html"});
      response.write("hello iain");