Validating Twilio Authy Callbacks

When using Webhooks with push authentications, Twilio will send a callback to your application's exposed URL when a user interacts with your ApprovalRequest. While testing, you can accept all incoming webhooks but in production you'll need to verify the authenticity of incoming requests.

Twilio sends an HTTP Header X-Authy-Signature with every outgoing request to your application. X-Authy-Signature is a HMAC signature of the full message body sent from Twilio hashed with your Application API Key (from Authy in the Twilio Console).

You can find complete code snippets here on Github.

Verify a Twilio Authy Callback

Checking the authenticity of the X-Authy-Signature HTTP Header is a 6 step process.

  • Create a string using the Webhook URL without any parameters
Loading Code Samples...
Language
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
Use the webhook URL without any parameters to create a string.
Create a Webhook URL String

Use the webhook URL without any parameters to create a string.

  • Sort the list of received parameters in case-sensitive order and convert them to URL format
Loading Code Samples...
Language
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
Sort all received parameters in alphabetical, case-sensitive order after converting them to URL format.
Sort the Parameters

Sort all received parameters in alphabetical, case-sensitive order after converting them to URL format.

  • Grab the nonce from the X-Authy-Signature HTTP Header
Loading Code Samples...
Language
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
Grab the nonce from the X-Authy-Signature HTTP Header.
Get the Nonce

Grab the nonce from the X-Authy-Signature HTTP Header.

  • Join the nonce, HTTP method ('POST'), and the sorted parameters together with the vertical pipe, ('|') character
Loading Code Samples...
Language
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
Using the vertical pipe ('\') character, join the nonce, HTTP Method, and sorted parameters.
Join the Nonce, Method, and Params

Using the vertical pipe ('\') character, join the nonce, HTTP Method, and sorted parameters.

  • Use HMAC-SHA256 to hash the string using your Application API Key
Loading Code Samples...
Language
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
Use HMAC-SHA256 to hash the resulting string with your Application API Key from the console.
Hash the Combined String with HMAC-SHA256

Use HMAC-SHA256 to hash the resulting string with your Application API Key from the console.

  • Base64 Encode the digest (as described in RFC 4648 - do not include line breaks)
Loading Code Samples...
Language
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
Follow RFC4648 to Base64 encode the digest
Encode the Digest with Base64

Follow RFC4648 to Base64 encode the digest

Here is every step summarized so you can get an idea of the whole process.

Loading Code Samples...
Language
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
Overview of the steps needed to verify an incoming Twilio webhook for Push Notifications.
Verify an Incoming Two-factor Authentication Webhook

Overview of the steps needed to verify an incoming Twilio webhook for Push Notifications.

Once you have encoded the digest, you can compare the resulting string with the X-Authy-Signature HTTP Header. If they match, the incoming request is from Twilio. If there is a mismatch, you should reject the request as fraudulent.

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

Loading Code Samples...
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false
SDK Version:
  • 5.x
SDK Version:
  • 7.x
SDK Version:
  • 3.x
SDK Version:
  • 5.x
SDK Version:
  • 6.x
SDK Version:
  • 5.x
const qs = require('qs');
const crypto = require('crypto');

/**
 * @param {http} req This is an HTTP request from the Express middleware
 * @param {!string} apiKey  Account Security API key
 * @return {Boolean} True if verified
 */
function verifyCallback(req, apiKey) {
  const url = req.headers['x-forwarded-proto'] + '://' + req.hostname + req.url;
  const method = req.method;
  const params = req.body;

  // Sort the params
  const sortedParams = qs
    .stringify(params, { arrayFormat: 'brackets' })
    .split('&')
    .sort(sortByPropertyOnly)
    .join('&')
    .replace(/%20/g, '+');

  // Read the nonce from the request
  const nonce = req.headers['x-authy-signature-nonce'];

  // concatinate all together and separate by '|'
  const data = nonce + '|' + method + '|' + url + '|' + sortedParams;

  // compute the signature
  const computedSig = crypto
    .createHmac('sha256', apiKey)
    .update(data)
    .digest('base64');

  const sig = req.headers['x-authy-signature'];

  // compare the message signature with your calculated signature
  return sig === computedSig;
}

/**
 * Sort by property only.
 *  Normal JS sort parses the entire string so a stringified array value like 'events=zzzz'
 *  would be moved after 'events=aaaa'.
 *
 *  For this approach, we split tokenize the string around the '=' value and only sort alphabetically
 *  by the property.
 *
 * @param {string} x
 * @param {string} y
 * @return {number}
 */
function sortByPropertyOnly(x, y) {
  const xx = x.split('=');
  const yy = y.split('=');

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
using System.Security.Cryptography;

namespace Test
{
    public class AuthyWebhook
    {
        private string Verify(System.Web.HttpRequestBase Request, string ApiKey)
        {
            // Read the nonce from the request
            var nonce = Request.Headers["x-authy-signature-nonce"];
            var method = Request.HttpMethod;
            var url = Request.Url.AbsoluteUri;
            var bodyRequest = new string [];
            foreach (string key in Request.Form.Keys)
            {
                bodyRequest.Add(key + "=" + Request.Form[key]);
            }
            // Sort the params
            var params = String.join("&", bodyRequest.Sort());

            // concatenate all together and separate by '|'
            var data = $"{nonce}|{method}|{url}|{params}";

            // compute the signature
            var encoding = new System.Text.ASCIIEncoding();
            byte[] ApiKeyBytes = encoding.GetBytes(ApiKey);
            byte[] DataBytes = encoding.GetBytes(data);
            using (var hmacsha256 = new HMACSHA256(ApiKeyBytes))
            {
                // Read the Authy Signature from the request
                var sig = Request.Headers["x-authy-signature"];

                // compare the message signature with your calculated signature
                byte[] hashmessage = hmacsha256.ComputeHash(DataBytes);
                return Convert.ToBase64String(hashmessage) == sig;
            }
        }
    }
}
<?php

function verifyWebhook($apiKey) {
    // Read the nonce from the request
    $nonce = $_SERVER['x-authy-signature-nonce'];
    $method = $_SERVER['REQUEST_METHOD'];
    $proto = isset($_SERVER['HTTPS']) ? "https" : "http";
    $url = "{$proto}://{$_SERVER[HTTP_HOST]}{$_SERVER[REQUEST_URI]}";
    $params = implode('&', array_map(function($k, $v) {
        return "$k=$v";
    }, array_keys($_POST), array_values($_POST)));
    sort($params);

    // concatenate all together and separate them by '|'
    $data = "$nonce|$method|$url|$params";

    // compute the signature
    $computedSig = base64_encode(hash_hmac('sha256', $data, $apiKey, true));

    // get the authy signature
    $sig = $_SERVER['x-authy-signature'];

    // compare the message signature with your calculated signature
    return hash_equals($computedSig, $sig);
}
require 'uri'

class CallbackVerifier

  # @param [request] A Rails request object
  # @param [api_key] The API key used to sign the request
  # @return [boolean] True if verified
  def verify_callback(request, api_key)
    url = url_for(:only_path => false, :overwrite_params=>nil)

    # Sort and join the parameters
    parameter_string = request
      .query_parameters
      .to_a
      .concat(request.request_parameters.to_a)
      .sort
      .map{ |p| p.join('=')}
      .join('&')

    parameter_string = URI.encode(parameter_string)

    # Read the nonce from the request
    nonce = request.headers['x-authy-signature-nonce']

    # Join all request parts using '|'
    data = "#{nonce}|#{request.method}|#{url}|#{parameter_string}"

    # Compute the signature
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.digest(digest, api_key, data)
    hash = Base64.encode64(hmac)

    # Extract the actual request signature
    signature = request.headers['x-authy-signature']

    # Compare the computed signature with the actual signature
    hash == signature
  end
end
import base64
import hashlib
import hmac

from django.conf import settings
from django.http import HttpResponseForbidden
from functools import wraps
try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


def validate_authy_request(f):
    """Validates that incoming requests genuinely originated from Twilio"""
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        url = request.build_absolute_uri('?')
        method = request.method
        params = getattr(request, method).items()
        sorted_params = urlencode(sorted(params))

        # Read the nonce from the request
        nonce = request.META['HTTP_X_AUTHY_SIGNATURE_NONCE']

        # Concatenate all together and separate by '|'
        data = '|'.join([nonce, method, url, sorted_params])

        # Compute the signature
        computed_dig = hmac.new(
            settings.ACCOUNT_SECURITY_API_KEY.encode('utf-8'),
            msg=data.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        computed_sig = base64.b64encode(computed_dig)

        sig = request.META['HTTP_X_AUTHY_SIGNATURE']

        # Compare the message signature with your calculated signature
        # Continue processing the request if it's valid, return a 403 error if
        # it's not
        if sig == computed_sig:
            return f(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()
    return decorated_function
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CallbackVerifier {

    private boolean verify(HttpServletRequest request, String apiKey) {
        String url = String.format("%s://%s%s", request.getScheme(),
                request.getServerName(), request.getServletPath());

        // Sort the params
        Map<String, String[]> sortedParameters =
                new TreeMap<>(request.getParameterMap());

        // Join and encode parameters
        List<String> flattenedParameters = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : sortedParameters.entrySet()) {
            for (String value : entry.getValue()) {
                flattenedParameters.add(entry.getKey() + "=" + value);
            }
        }
        String parameterString = String.join("&", flattenedParameters);

        // Read the nonce from the request
        String nonce = request.getHeader("x-authy-signature-nonce");

        // Join all the request bits using '|'
        String data = String.format("%s|%s|%s|%s",
                                    nonce,
                                    request.getMethod(),
                                    url,
                                    parameterString);

        try {
            // Compute the signature
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(apiKey.getBytes(),
                    "HmacSHA256");
            sha256_HMAC.init(secret_key);
            String hash = Base64
                    .getEncoder()
                    .encodeToString(sha256_HMAC.doFinal(data.getBytes()));

            // Extract the actual request signature
            String signature = request.getHeader("x-authy-signature");

            // Compute the request signature with your computed signature
            return hash.equals(signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }
}
testable: false