Message Security

Each webhook can optionally define a secret cryptographic key in the HMAC SHA256 Secret field. FastSpring servers will use that key to generate a hashed digest of each webhook payload. The resulting digest will be encoded to base64 and included in the X-FS-Signature header of the webhook. Your server can then use the same process, creating a hashed digest of the payload using the same secret key on your side, and then encoding the resulting hash to base64 before comparing the it to the value in that header.

A post with a valid, matching digest in the header can only have originated from a source that uses the correct secret key. If the key has been provided only to FastSpring, via the webhook interface in the FastSpring App (i.e., not used anywhere else), this confirms that the webhook data is authentic. You can find more information about hash-based message authentication at https://en.wikipedia.org/wiki/Hash-based_message_authentication_code.

The X-FS-Signature header sent by FastSpring is not case sensitive and might be sent with varying case (all lower case, or mixed case). We recommend capturing the incoming webhook data--including the header--for verification while adding/registering. The console will log the request and response so that the header contents can be inspected.

The following is an example of using the PHP hash_hmac function to create the hashed digest, where $secret is the secret key entered in the HMAC SHA256 Secret field in the FastSpring App:

Example of using PHP to compute and compare the HMAC hash

Note: nginx users need to enable underscores in headers to use this.

$hash = base64_encode( hash_hmac( 'sha256', file_get_contents('php://input') , $secret, true ) ); if ($hash == $_SERVER['X-Fs-Signature']) { /* Your code here */ }

Example of using Node.js to compute and compare the HMAC hash

const crypto = require('crypto');


/**
 * Validates a FastSpring webhook
 *
 * @param {string} req    Node request object, where 'req.body' is a Node
 *                        Buffer object containing the request body
 * @param {string} secret the secret string saved in the FastSpring App
 */
const isValidSignature = (req, secret) => {
  const fsSignature = req.headers['X-FS-Signature'];
  const computedSignature = crypto.createHmac('sha256', secret)
    .update(req.body)
    .digest()
    .toString('base64');
  return fsSignature === computedSignature;
}

IP Filtering

Another method of increasing confidence that webhooks are being sent by Fastspring is to compare the IP which sent the request. Webhooks delivered from API will come from 107.23.30.83. However, IP addresses can be spoofed so IP detection is not entirely secure.


Did this page help you?