Signature Validation-V3

    Introduction

    All requests made to your application server by Plivo’s Voice platform contain X-Plivo-Signature-V3, X-Plivo-Signature-Ma-V3, and X-Plivo-Signature-V3-Nonce HTTP headers.

    To verify that the request to your server has originated from Plivo and that it has not been altered en route, you may generate your own request signatures and compare them with the X-Plivo-Signature-V3 or X-Plivo-Signature-Ma-V3 HTTP headers passed by Plivo.

    Note: X-Plivo-Signature-V3 is generated using the Auth Token of the associated main account or sub-account and the X-Plvo-Signature-Ma-V3 is always generated using the main account’s Auth Token.

    Signature Generation Logic

    Plivo signs all HTTP requests from its servers to your application server. The logic used to generate these signatures is described below.

    Assembling the complete request

    Plivo assembles the request to your application server by concatenating the final request URL (the full URL with the scheme, port, and query string) and any POST parameters.

    • If your request is a POST, Plivo takes all the POST parameters, sorts them alphabetically by name (using Unix style case sensitive sorting), and concatenates the parameter name and value pairs to the end of the URL.
    • If the request is a GET, the final request URL would include all of Plivo’s request parameters appended in the query string of your original URL.

    Example:

    If your application URL is https://example.com/abcd?foo=bar and the list of Plivo POST params is:

    - Digits:1234
    - To:+15555555555
    - From:+15551111111
    - CallUUID:4vbcpem8-0u46-x1ha-9af1-438vc92bf374
    

    Then the complete assembled request string would be:

    - https://example.com/abcd?foo=bar.CallUuid4vbcpem8-0u46-x1ha-9af1-438vc92bf374Digits1234From+15551111111To+15555555555
    

    Concatenating with Nonce value

    The assembled request string from step one is then appended with a randomly generated ‘Nonce’ string which is unique for every request. This string is also passed separately in the HTTP header X-Plivo-Signature-V3-Nonce as part of the HTTP request to your application URL.

    Example:

    If the randomly generated Nonce value is:

    kjsdhfsd87sd7yisud2
    

    Then The following result would occur after concatenating it to the assembled request string from step one above

    - https://example.com/abcd?foo=bar.CallUuid4vbcpem8-0u46-x1ha-9af1-438vc92bf374Caller+15551111111Digits1234From+15551111111To+15555555555.kjsdhfsd87sd7yisud2
    

    Since Nonce values are unique, they are a great tool to protect your application server from replay attacks.

    Signing with Auth Token

    The output of step two above is then signed using HMAC-SHA256 and your Plivo Auth Tokens as the keys. The resulting signed hashes are Base 64 encoded and passed in HTTP headers with the request.

    X-Plivo-Signature-V3 is generated using the Auth Token of the account or sub-account associated with the request entity.

    For example, if the request is for an incoming call to a Plivo phone number mapped to one of your sub-accounts, then that sub-account’s Auth Token will be used when generating X-Plivo-Signature-V3.

    X-Plivo-Signature-Ma-V3 on the other hand, is always generated using the Auth Token of your main Plivo account.

    Note: X-Plivo-Signature-V3 If you currently have more than one active Auth Token for the associated account or sub-account, Plivo will generate a signature using each of the active Auth Tokens. A comma-separated list of the resulting signatures will be passed (for example, X-Plivo-Signature-Ma-V3: <Signature signed with Token 1>,<Signature signed with Token 2>).

    Validating requests on app server

    Plivo Server-Side SDKs include helper functions to validate incoming requests from the Plivo Voice platform using the X-Plivo-Signature HTTP headers.

    Code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    from flask import Flask, request, make_response, url_for
    import plivo
    from urllib.parse import urlparse, parse_qs
    from plivo import utils
    from plivo import plivoxml
    
    app = Flask(__name__)
    
    @app.route('/speak/', methods=['GET', 'POST'])
    def validate_signature():
        signature = request.headers.get('X-Plivo-Signature-V3', 'signature')
        nonce = request.headers.get('X-Plivo-Signature-V3-Nonce', '12345')
        url = url_for('validate_signature', _external=True)
        auth_token = "your_auth_token"
        method = request.method
        if method == 'GET':
            valid = plivo.utils.validate_v3_signature(
                method, url, nonce, auth_token, signature)
        else:
            params = request.get_json()
            valid = plivo.utils.validate_v3_signature(
                method, url, nonce, auth_token, signature, params)
        print(valid)
        r = plivoxml.ResponseElement()
        speak_params = {
            'loop': '3'
        }
        r.add(plivoxml.SpeakElement("Hello, from Plivo", **speak_params))
        response = make_response(r.to_string())
        response.headers["Content-type"] = "text/xml"
        print(r.to_string())
        return response
    
    
    if __name__ == "__main__":
        app.run(host='0.0.0.0', debug=True)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    require 'sinatra'
    require 'rubygems'
    require 'plivo'
    include Plivo
    include Plivo::XML
    
    get '/speak/' do
    	auth_token = "Your_Auth_Token"
    	signature = headers.fetch("X-Plivo-Signature-V3", "signature")
    	nonce = headers.fetch("X-Plivo-Signature-V3-Nonce", "12345")
    	url = request.url
    	method = "GET"
    	output = Plivo::Utils.valid_signatureV3?(url, nonce, signature, auth_token, method)
    	puts output
    
    	response = Response.new
    	response.addSpeak("Hello, Welcome to Plivo")
    	xml = PlivoXML.new(response)
    	content_type 'text/xml'
    	return xml.to_s
    end
    
    post '/speak/' do
    	auth_token = "your_auth_token"
    	signature = headers.fetch("X-Plivo-Signature-V3", "signature")
    	nonce = headers.fetch("X-Plivo-Signature-V3-Nonce", "12345")
    	url = request.url
    	method = "POST"
    	output = Plivo::Utils.valid_signatureV3?(url, nonce, signature, auth_token, method, params)
    	puts output
    
    	response = Response.new
    	response.addSpeak("Hello, Welcome to Plivo")
    	xml = PlivoXML.new(response)
    	content_type 'text/xml'
    	return xml.to_s
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    let express = require('express');
    let app = express();
    app.set('port', (process.env.PORT || 5000));
    app.use(express.static(__dirname + '/public'));
    
    app.all('/speak/', function (request, response) {
        let headers = request.headers;
        console.log(headers);
        let signature = request["X-Plivo-Signature-V3"];
        let nonce = request["X-Plivo-Signature-V3-Nonce"];
        if (!signature) {
            signature = "signature";
        }
        if (!nonce) {
            nonce = "12345";
        }
        let url = request.url;
        let auth_token = "your_auth_token";
        console.log(signature, nonce);
        let method = request.method;
        let validate;
        if (method == "GET") {
            validate = plivo.validateV3Signature(method, url, nonce, auth_token, signature);
        } else {
            let params = request.body;
            validate = plivo.validateV3Signature(method, url, nonce, auth_token, signature, params);
        }
        console.log(validate);
    
        let r = plivo.Response();
        r.addSpeak("Hello from Plivo");
        console.log(r.toXML());
    
        response.set({
            'Content-Type': 'text/xml'
        });
        response.end(r.toXML());
    });
    
    app.listen(app.get('port'), function () {
        console.log('Node app is running on port', app.get('port'));
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    <?php
    require 'vendor/autoload.php';
    use Plivo\Exceptions\PlivoValidationException;
    use Plivo\Util\v3SignatureValidation;
    use Plivo\XML\Response;
    
    if (preg_match('/speak/', $_SERVER["REQUEST_URI"])) {
        $auth_token = "your_auth_token";
        $signature = @$_SERVER["X-Plivo-Signature-V3"] ?: 'signature';
        $nonce = @$_SERVER["X-Plivo-Signature-V3-Nonce"] ?: 'nonce';
        $url = 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . "{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
        $method = $_SERVER['REQUEST_METHOD'];
        $SVUtil = new v3SignatureValidation();
        if ($method == "GET") {
            try {
                $valid = $SVUtil->validateV3Signature($method, $url, $nonce, $auth_token, $signature);
            } catch (PlivoValidationException $e) {
                echo("error");
            }
        } else {
            $body = file_get_contents("php://input");
            $params = json_decode($body, true);
            try {
                $valid = $SVUtil->validateV3Signature($method, $url, $nonce, $auth_token, $signature, $params);
            } catch (PlivoValidationException $e) {
                echo("error");
            }
        }
        echo $valid;
        $body = 'Hi, Calling from Plivo';
        $attributes = array(
            'loop' => 3,
        );
        $r = new Response();
        $r->addSpeak($body, $attributes);
        echo($r->toXML());
    } else {
        echo "<p>Welcome to Plivo</p>";
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    
    package plivoexample;
    
    import java.io.IOException;
    import java.net.URLDecoder;
    import java.util.Iterator;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    
    import com.plivo.helper.exception.PlivoException;
    import com.plivo.helper.xml.elements.Message;
    import com.plivo.helper.xml.elements.PlivoResponse;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.servlet.ServletContextHandler;
    import org.eclipse.jetty.servlet.ServletHolder;
    
    import com.plivo.helper.util.*;
    
    public class validateSignature extends HttpServlet {
      private static final long serialVersionUID = 1L;
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        String auth_token = "your_auth_token";
        String signature = req.getHeader("X-Plivo-Signature-V3");
        String nonce = req.getHeader("X-Plivo-Signature-V3-Nonce");
        String url = req.getRequestURL().toString();
        String method = req.getMethod();
    
        if(method == "GET") {
          try {
            Boolean isValid = Utils.validateSignatureV3(url, nonce, signature, auth_token, method);
            System.out.println("Valid : " + isValid);
          } catch (PlivoException e) {
            e.printStackTrace();
          }
        }
        else{
          try {
            Map<String, String> params = req.getParameterMap();
            Boolean isValid = Utils.validateSignatureV3(url, nonce, signature, auth_token, method, params);
            System.out.println("Valid : " + isValid);
          } catch (PlivoException e) {
            e.printStackTrace();
          }
        }
    
        PlivoResponse response = new PlivoResponse();
        Speak spk = new Speak("Hello, Welcome to Plivo");
    
        try {
          response.append(spk);
          System.out.println(response.toXML());
          resp.addHeader("Content-Type", "text/xml");
          resp.getWriter().print(response.toXML());
        } catch (PlivoException e) {
          e.printStackTrace();
        }
      }
    
      public static void main(String[] args) throws Exception {
        String port = System.getenv("PORT");
        if(port==null)
          port ="8000";
        Server server = new Server(Integer.valueOf(port));
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        server.setHandler(context);
        context.addServlet(new ServletHolder(new  validateSignature()),"/speak");
        server.start();
        server.join();
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"io/ioutil"
    	"net/http"
    	"reflect"
    	"unsafe"
    
    	"github.com/plivo/plivo-go"
    	"github.com/plivo/plivo-go/xml"
    	"github.com/sirupsen/logrus"
    )
    
    type Message struct {
    	Id   string `json:"id"`
    	Name string `json:"name"`
    }
    
    func BytesToString(b []byte) string {
    	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    	sh := reflect.StringHeader{bh.Data, bh.Len}
    	return *(*string)(unsafe.Pointer(&sh))
    }
    
    func speak(w http.ResponseWriter, request *http.Request) {
    	url := request.Host + request.RequestURI
    	signature := request.Header.Get("X-Plivo-Signature-V3")
    	nonce := request.Header.Get("X-Plivo-Signature-V3-Nonce")
    	method := request.Method
    	authToken := "your_auth_token"
    	var valid bool
    	if method == "GET" {
    		valid = plivo.ValidateSignatureV3(url, nonce, method, signature, authToken)
    	} else {
    		parameters := make(map[string]string)
    		b, err := ioutil.ReadAll(request.Body)
    		defer request.Body.Close()
    		if err != nil {
    			http.Error(w, err.Error(), 500)
    			return
    		}
    
    		var msg Message
    		err = json.Unmarshal(b, &msg)
    		if err != nil {
    			http.Error(w, err.Error(), 500)
    			return
    		}
    
    		var inInterface map[string]interface{}
    		inrec, _ := json.Marshal(msg)
    		json.Unmarshal(inrec, &inInterface)
    		for field, val := range inInterface {
    			parameters[field] = val.(string)
    		}
    
    		w.Header().Set("content-type", "text/xml")
    		valid = plivo.ValidateSignatureV3(url, nonce, method, signature, authToken, parameters)
    	}
    	logrus.Info(valid)
    	response := xml.ResponseElement{
    		Contents: []interface{}{
    			new(xml.SpeakElement).
    				SetContents("Go Green, Go Plivo."),
    		},
    	}
    	fmt.Fprintf(w, response.String())
    }
    
    func main() {
    	http.HandleFunc("/speak/", speak)
    	http.ListenAndServe(":5000", nil)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using RestSharp;
    using Plivo.XML;
    using Plivo;
    using Nancy;
    
    namespace plivo_dotnet_app
    {
        public sealed class Program : NancyModule
        {
            public Program()
            {
                Get("/speak/", x =>
                {
                    string signature = Request.Headers["X-Plivo-Signature-V3"].ToString();
                    string nonce = Request.Headers["X-Plivo-Signature-V3_Nonce"].ToString();
                    string auth_token = "your_auth_token";
                    string method = Request.Method;
                    string url = Request.Url;
                    bool valid;
                    Dictionary<string, string> parameters = new Dictionary<string, string>();
                    valid = Plivo.Utilities.XPlivoSignatureV3.VerifySignature(url, nonce, signature, auth_token, method);
                    Debug.WriteLine("Valid : " + valid);
    
                    Plivo.XML.Response resp = new Plivo.XML.Response();
                    resp.AddSpeak("Hello, Welcome to Plivo", parameters);
                    Debug.WriteLine(resp.ToString());
    
                    var output = resp.ToString();
                    var res = (Nancy.Response) output;
                    res.ContentType = "text/xml";
                    return res;
                });
    
                Post<Response>("/speak/", x =>
                {
                    string signature = Request.Headers["X-Plivo-Signature-V3"].ToString();
                    string nonce = Request.Headers["X-Plivo-Signature-V3_Nonce"].ToString();
                    string auth_token = "your_auth_token";
                    string method = Request.Method;
                    string url = Request.Url;
                    bool valid;
                    Dictionary<string, string> parameters = new Dictionary<string, string>();
                    parameters = Request.Form;
                    valid = Plivo.Utilities.XPlivoSignatureV3.VerifySignature(url, nonce, signature, auth_token, method, parameters);
                    Debug.WriteLine("Valid : " + valid);
    
                    Plivo.XML.Response resp = new Plivo.XML.Response();
                    resp.AddSpeak("Hello, Welcome to Plivo", parameters);
                    Debug.WriteLine(resp.ToString());
    
                    var output = resp.ToString();
                    var res = (Nancy.Response) output;
                    res.ContentType = "text/xml";
                    return res;
                });
            }
            
            static void Main(string[] args)
            {
                var p = new Program();
            }
        }
    }
    

    You may also write your own request validation code by generating request signatures based on the Signature Generation Logic explained above, and validating them against the signatures passed by Plivo in the HTTP request headers.

    Deprecation notice: X-Plivo-Signature-V2

    If you’re currently using Plivo’s V2 Signatures to validate requests, we highly recommend switching over to the new and enhanced X-Plivo-Signature-V3. V3 signatures offer improved protection against manipulation of POST request payloads en route.

    Click here for more information on X-Plivo-Signature-V2.