# Validating Twilio Authy Callbacks

> \[!WARNING]
>
> As of November 2022, Twilio no longer provides support for Authy SMS/Voice-only customers. Customers who were also using Authy TOTP or Push prior to March 1, 2023 are still supported. The Authy API is now closed to new customers and will be fully deprecated in the future.
>
> For new development, we encourage you to use the [Verify v2 API](/docs/verify/api).
>
> Existing customers will not be impacted at this time until Authy API has reached End of Life. For more information about migration, see [Migrating from Authy to Verify for SMS](https://www.twilio.com/blog/migrate-authy-to-verify).

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](https://www.twilio.com/console/2fa)).

You can find [complete code snippets here](https://github.com/twilio-samples/api-snippets/tree/main/two-factor-authentication/verify-webhook) 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

Create a Webhook URL String

```js
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.protocol + '://' + req.get('host') + req.originalUrl;
  const method = req.method;
  const params = req.body;	// needs `npm i body-parser` on Express 4

  // 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('=')[0];
  const yy = y.split('=')[0];

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
```

```rb
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 on Rails
    json_params = JSON.parse(request.body.read)
    parameter_string = json_params.to_query

    # 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.strict_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
```

* Flatten the received JSON body and sort this list in case-sensitive order and convert them to URL format

Sort the Parameters

```js
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.protocol + '://' + req.get('host') + req.originalUrl;
  const method = req.method;
  const params = req.body;	// needs `npm i body-parser` on Express 4

  // 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('=')[0];
  const yy = y.split('=')[0];

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
```

```rb
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 on Rails
    json_params = JSON.parse(request.body.read)
    parameter_string = json_params.to_query

    # 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.strict_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
```

* Grab the nonce from the `X-Authy-Signature` HTTP Header

Get the Nonce

```js
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.protocol + '://' + req.get('host') + req.originalUrl;
  const method = req.method;
  const params = req.body;	// needs `npm i body-parser` on Express 4

  // 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('=')[0];
  const yy = y.split('=')[0];

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
```

```rb
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 on Rails
    json_params = JSON.parse(request.body.read)
    parameter_string = json_params.to_query

    # 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.strict_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
```

* Join the nonce, HTTP method ('`POST`'), and the sorted parameters together with the vertical pipe, ('|') character

Join the Nonce, Method, and Params

```js
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.protocol + '://' + req.get('host') + req.originalUrl;
  const method = req.method;
  const params = req.body;	// needs `npm i body-parser` on Express 4

  // 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('=')[0];
  const yy = y.split('=')[0];

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
```

```rb
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 on Rails
    json_params = JSON.parse(request.body.read)
    parameter_string = json_params.to_query

    # 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.strict_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
```

* Use HMAC-SHA256 to hash the string using your Application API Key

Hash the Combined String with HMAC-SHA256

```js
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.protocol + '://' + req.get('host') + req.originalUrl;
  const method = req.method;
  const params = req.body;	// needs `npm i body-parser` on Express 4

  // 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('=')[0];
  const yy = y.split('=')[0];

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
```

```rb
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 on Rails
    json_params = JSON.parse(request.body.read)
    parameter_string = json_params.to_query

    # 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.strict_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
```

* Base64 Encode the digest (as described in [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648) - do not include line breaks)

Encode the Digest with Base64

```js
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.protocol + '://' + req.get('host') + req.originalUrl;
  const method = req.method;
  const params = req.body;	// needs `npm i body-parser` on Express 4

  // 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('=')[0];
  const yy = y.split('=')[0];

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
```

```rb
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 on Rails
    json_params = JSON.parse(request.body.read)
    parameter_string = json_params.to_query

    # 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.strict_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
```

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

Verify an Incoming Two-factor Authentication Webhook

```js
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.protocol + '://' + req.get('host') + req.originalUrl;
  const method = req.method;
  const params = req.body;	// needs `npm i body-parser` on Express 4

  // 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('=')[0];
  const yy = y.split('=')[0];

  if (xx < yy) {
    return -1;
  }
  if (xx > yy) {
    return 1;
  }
  return 0;
}
```

```rb
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 on Rails
    json_params = JSON.parse(request.body.read)
    parameter_string = json_params.to_query

    # 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.strict_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
```

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.
