Verifying callback requests

For applications that process protected health information (PHI) or other sensitive data, it is a good practice to verify that callback requests are in fact coming from Phaxio servers and not a malicious third party.

Due to the autoscaling nature of the clusters that perform callback requests, Phaxio does not publish an IP whitelist that can be used to restrict incoming traffic in your firewall. Instead you can use HTTP Authentication or the X-Phaxio-Signature header to secure your application for callbacks.

Additionally, we recommend that all callback URLs provided to Phaxio use TLS.

HTTP Authentication

You can provide a username and password in your callback URL and Phaxio will use these credentials when making requests. For example, for a URL https://example.com/phaxio/callbacks with username foo and password bar, you can pass Phaxio a URL in the following format and Phaxio will call it with HTTP authentication headers:

https://foo:bar@example.com/phaxio/callbacks

Callback tokens and X-Phaxio-Signature header

Every callback request contains a X-Phaxio-Signature header that can be used to verify the request. Phaxio generates a string that describes the content of the request and signs it using your account’s unique callback token. Your callback token can be found on your Callbacks Settings page. To validate a request your application can recreate the signature and compare it to the X-Phaxio-Signature header.

To generate the signature:

  1. Take the URL string that you’ve submitted to Phaxio, including any trailing slashes.
  2. Sort all POST parameters by name.
  3. For each POST parameter, concatenate its name and value without any delimiters to the URL in step 1.
  4. Sort any file parts in the request by name.
  5. For each file part, concatenate its part name and SHA1 digest of its file contents to the resulting string in step 2.
  6. Sign the resulting string with HMAC-SHA1 using your Callback Token as the key.

Most official Phaxio client libraries contain helper functions for generating callback signatures. Additionally, here are some code examples that can be used to validate requests.

/**
* @param string $token The callback token that signed the signature. Obtainable from https://www.phaxio.com/apiSettings/callbacks
* @param string $url The full URL that was called by Phaxio, including the query string
* @param mixed $postParameters An associative array of the POST parameters
* @param string $signature The X-Phaxio-Signature HTTP header value
* @return boolean
*/
public function isValidCallbackRequest($token, $url = null, $postParameters = null, $uploadedFiles = null, $signature = null){
    if (!$url) {
        $url = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    }

    if (!$postParameters){
        $postParameters = $_REQUEST;
    }

    if (!$uploadedFiles){
        $uploadedFiles = $_FILES;
    }

    if (!$signature){
        $signature = $_SERVER['HTTP_X_PHAXIO_SIGNATURE'];
    }

    // sort the array by keys
    ksort($postParameters);

    // append the data array to the url string, with no delimiters
    foreach ($postParameters as $key => $value) {
        $url .= $key . $value;
    }

    foreach ($uploadedFiles as $key => $value) {
        $url .= $key . sha1_file($value['tmp_name']);
    }

    $hmac = hash_hmac("sha1", $url, $token);
    return $signature == $hmac;
}
function generateSignature(url, req, callbackToken) {
    //sort the POST fields first and add them to the url
    var names = [];
    for (var idx in req.body) names.push(idx);
    names.sort();

    for (var idx = 0; idx < names.length; idx++) {
        url += names[idx] + req.body[names[idx]];
    }

    //sort the file parts and add their SHA1 sums to the URL
    var fileNames = [];
    var fieldNamePaths = {};
    for (var idx in req.files){
        var fieldname = req.files[idx].fieldname;
        fileNames.push(fieldname);
        fieldNamePaths[fieldname] = req.files[idx].path;
    }
    fileNames.sort();

    for (var idx = 0; idx < fileNames.length; idx++) {
        var fileSha1Hash = crypto.createHash('sha1').update(fs.readFileSync(fieldNamePaths[fileNames[idx]])).digest('hex');
        url += fileNames[idx] + fileSha1Hash;
    }

    return crypto.createHmac('sha1', callbackToken).update(url).digest('hex');
}
def validate_signature(token, url, parameters, files, signature):
    # sort the post fields and add them to the URL
    for key in sorted(parameters.keys()):
        url += '{}{}'.format(key, parameters[key])

    # sort the files and add their SHA1 sums to the URL
    for filename in sorted(files.keys()):
        file_hash = hashlib.sha1()
        file_hash.update(files[filename].read())
        files[filename].stream.seek(0)
        url += '{}{}'.format(filename, file_hash.hexdigest())

    return signature == hmac.new(key=token.encode('utf-8'), msg=url.encode('utf-8'), digestmod=hashlib.sha1).hexdigest()