Documentation You are here: start » v » 2.0 » hmac_validation

HMAC Product Verification: Locking Down your Add-To-Cart Links and Forms

The What & Why

What Is FoxyCart's HMAC Product Verification?

FoxyCart's HMAC Product Verification is a cryptographic method to prevent tampering with your product links and forms, available as of FoxyCart v0.7.0, based on HMAC SHA-256. We know that sounds terrifying, but please read on and it will make more sense. (And great news! You can likely implement this without actually having to know anything about the details.)

We recommend taking a moment to familiarize yourself with crypographic hash functions, as they're quite interesting, but the short version is this: Using a hash (specifically with HMAC) provides a very secure way to ensure data isn't modified. So your add-to-cart links and forms can be locked down, preventing a nefarious user from modifying your products.

Note that hashing (or “signing”) isn't the same thing as encryption, though they're related. Encryption allows for decryption; it's reversible. Hashing, on the other hand, is one way. You can get a unique hash from your source, but you can't* get the source from the hash. For this reason, it's useful to “sign” things to ensure they haven't been tampered with, but it doesn't “hide” data. For what we care about here, that's perfect.

We'll use the terms “signing” and “hashing” interchangeably on this page.

The Potential Risks & Solutions

If you do not implement FoxyCart's HMAC cart verification functionality then your products are relatively easily modifiable by web-savvy users. This means that your Product A sold at $20 could be added to the cart at $10 or $1, or added to the cart with the wrong category (possibly affecting taxes or shipping) or etc.

While this certainly can be a major problem, there are multiple ways to minimize the risk. The most common approach taken by stores with smaller order volume is “eyeballing” orders and doing sanity checks prior to delivery. If only one or two people handle the fulfillment and are familiar with the products and pricing, that is often enough to prevent fulfillment of a spoofed order.

Another option is to verify orders automatically using the XML datafeed, comparing the transactions and products against the prices stored in your site's database and alerting on error. Many higher-volume stores take this approach, and it is built into some of the FoxyCart integrations supplied by 3rd parties.

While both of those options can work well depending on your needs, the most robust method of product validation is to use FoxyCart's HMAC Verification. This method prevents spoofing attempts by requiring all links and form values to be “signed” using your “secret” (which in this case is your store's "secret key" available from the “advanced” setting page). Only values signed with your secret key will be accepted by FoxyCart, allowing you to prevent product spoofing before orders are placed.

Do You Need FoxyCart's HMAC Product Verification?

It really depends on a few things, including your order volume, your order processing flow, the nature of your products, and your cost to implement versus the potential payoff. That said, if you're worried about price spoofing we strongly recommend implementing it. If you only have a few products then it's only a little extra work up front. If you have many products then it can be added to your CMS and handled automatically by your site, in the background.

Language and Site Independent Solutions

Regardless of how you've built your site, you can use our Cloudflare Worker script, along with Cloudflare, to automatically protect your site. More info is here:

Please let us know if you run into trouble getting that set up.

Language Specific Examples & Helper Files

While you certainly can build your validation functionality from scratch, it may be a boon to use an existing library. Currently we only have official code for PHP but if you're interested in porting the PHP version to another language please let us know and we'd be happy to provide assistance.

Webflow: Pre-Payment Webhook

If you're using Webflow, you can set up a serverless function on Netlify.com to verify your product prices against your Webflow product collection. This is slightly technical, but hopefully will be more straightforward than it looks, and will generally be free unless you're doing really heavy volume. Setting it up starts with a single click.

Let us know if you need help getting it set up.

PHP: Automatic Validation with Minimal Effort

FoxyCart Cart Validation, PHP (on GitHub) to automatically sign an entire HTML page (recommended) or to sign individual links, inputs. This code is capable of automatically signing all links and forms on an entire HTML page, which makes it trivially easy to add to an existing site or CMS. Read the wiki on GitHub for instructions on how to implement this PHP script.

PHP: Helper Function

If you don't want to sign an entire HTML page at once but to make your own links, you can use this handy function:

function get_verification($var_name, $var_value, $var_code, $var_parent_code = "", $for_value = false) {
	$api_key = "your_api_key_here";
	$encodingval = htmlspecialchars($var_code . $var_parent_code . $var_name . $var_value, ENT_COMPAT);
	$label = ($for_value) ? $var_value : $var_name;
	return $label . '||' . hash_hmac('sha256', $encodingval, $api_key) . ($var_value === "--OPEN--" ? "||open" : "");
}

With this function, for each product you want to hash, you need to provide the name, value, and code to it. The name and value arguments are the name and value pair of the particular product attribute you're hashing, and the code argument is the product's code value. Note the value you pass off to the get_verification function should still be the actual value, not a url encoded version.

This function will return a hash based on the arguments provided - and return something that would look like this:

mycode||of02h1340wef9neq230sfnwr4340adn7z

Example

Take for example the following product: “Flute Warm-Up Book” priced at $1.99 with an SKU of “warmups”.

To get the hash for the code, you would call the function like this: get_verification('code', 'warmups', 'warmups'). Likewise, for the product name it would be get_verification('name', 'Flute Warm-Up Book', 'warmups') and the price would be get_verification('price', '1.99', 'warmups)

In a form, in it's vanilla, unsigned state, that would look like this:

<input type="hidden" name="name" value="Flute Warm-Up Book" />
<input type="hidden" name="code" value="warmups" />
<input type="hidden" name="price" value="1.99" />

Updated to include the helper function would look like this:

<input type="hidden" name="<?php echo get_verification('name', 'Flute Warm-Up Book', 'warmups'); ?>" value="Flute Warm-Up Book" />
<input type="hidden" name="<?php echo get_verification('code', 'warmups', 'warmups'); ?>" value="warmups" />
<input type="hidden" name="<?php echo get_verification('price', '1.99', 'warmups'); ?>" value="1.99" />

Where should the signature be placed in a form element? Note that the signature can be appended to either the name or the value of an input. Either will work just fine, although for radio inputs and select dropdowns, the signature must be appended to the value as the name is the same for all options of the field. See the implementation details for more information on that.

In a link in it's vanilla state would look like this:

https://yourdomain.foxycart.com/cart?name=Flute%20Warm-Up%20Book&code=warmups&price=1.99

Using PHP you could build out the same link using the helper function like this:

$atc = "https://yourdomain.foxycart.com/cart";
$atc .= "?" . get_verification("name", "Flute Warm-Up Book", "warmups") . "=Flute%20Warm-Up%20Book";
$atc .= "&" . get_verification("code", "warmups", "warmups") . "=warmups";
$atc .= "&" . get_verification("price", "1.99", "warmups") . "=1.99";

Bundled Products

If you're signing a link or form for a bundled product (say, a $1.99 warm-up exercises book comes with the purchase of a C Flute), you need to also specify the optional parent_code value to the get_verification() function.

Example: Building a form element in PHP

If the example product above also belonged to a parent product with a code of cflute, the form verification would look like this, including the parent product, “C Flute”:

<form method="POST" action="https://yourdomain.foxycart.com/cart">
 
// Parent
<input type="hidden" name="<?php echo get_verification('code', 'cflute', 'cflute'); ?>" value="cflute" />
<input type="hidden" name="<?php echo get_verification('name', 'C Flute', 'cflute'); ?>" value="C Flute" />
<input type="hidden" name="<?php echo get_verification('price', '13999', 'cflute'); ?>" value="13999" />
 
// Child
<input type="hidden" name="<?php echo "2:" .get_verification('code', 'warmups', 'warmups', 'cflute'); ?>" value="warmups" />
<input type="hidden" name="<?php echo "2:" .get_verification('name', 'Flute Warm-Ups', 'warmups', 'cflute'); ?>" value="Flute Warm-Ups" />
<input type="hidden" name="<?php echo "2:" .get_verification('price', '1.99', 'warmups', 'cflute'); ?>" value="1.99" />
<input type="hidden" name="<?php echo "2:" .get_verification('parent_code', 'cflute', 'warmups', 'cflute'); ?>" value="cflute" />
 
<input type="submit" value="Add to cart">
 
</form>
   $atc = 'https://yourdomain.foxycart.com/cart';
   // Parent
   $atc .= '?' . get_verification('code', 'cflute', 'cflute') . "=cflute";
   $atc .= "&" . get_verification('name', 'C Flute', 'cflute') . "=C%20Flute";
   $atc .= "&" . get_verification('price', '13999', 'cflute') . "=13999";
   // Child
   $atc .= '&2:' . get_verification('code', 'warmups', 'warmups', 'cflute') . "=warmups";
   $atc .= "&2:" . get_verification('name', 'Flute Warm-Up Book', 'warmups', 'cflute') . "=Flute%20Warm-Up%20Book";
   $atc .= "&2:" . get_verification('price', '1.99', 'warmups', 'cflute') . "=1.99";
   $atc .= "&2:" . get_verification('parent_code', 'cflute', 'warmups',  'cflute') . "=cflute";
 
   echo $atc . "\n";
 

Appending hash to value (select and radio inputs)

If you're signing a select or radio input element in a form, you need to append the verification to the value attribute rather than the name. To do this, simply specify true for the optional for_value argument like this:

<select name="color">
  <option value="<?php echo get_verification('color', 'red', 'mycode', '', true); ?>">Red</option>
  <option value="<?php echo get_verification('color', 'blue', 'mycode', '', true); ?>">Blue</option>
  <option value="<?php echo get_verification('color', 'yellow', 'mycode', '', true); ?>">Yellow</option>
</select>

Note there is a empty string for the fourth argument for the get_verification() function calls above. This is for the parent_code argument, which is only needed if your product is a bundled product.

User editable fields

If an input does not have a static value (such as a user-editable field), you can enter --OPEN-- for the value. For example, that could look like this within your form before signing:

<input type="text" name="custom_text" value="--OPEN--">

After signing, it will include ||open at the end of the signature, signifying that any value can be entered into that field, although the fields name will still be validated. To be clear, you won't add the ||open suffix yourself, that will be handled by the signing process. The above input would look something like this after signing:

<input type="hidden" name="custom_text||5310440bd680e17e0a556758ffa04452162b978490bfcb2af628cff83f7bb88f||open" value="--OPEN--">

If you are signing through the administration, you will see the --OPEN-- value in the input after successfully signing the form. You can remove the value at that point, and replace it with whatever default value you want.

Special Notes: If you values have spaces or special characters, see the Important Notes section below. Some name/value pairs also don't need to be signed - view the Excluded Name/Value Pairs section for information on that.

Manual Signing via FoxyCart Administration

If you're building a static HTML site, or just want to sign a couple forms, you can manually sign a form from your stores FoxyCart administration.

  1. Firstly, build out the link or form as you normally would, building it out entirely.
  2. On the Sample Code page of your stores FoxyCart administration, paste your link or form (or multiples thereof) into the text box under 'Step 2', replacing the code that was already in there. Ensure that your links are complete link elements and any forms are complete forms wrapped in a form element with the action attribute pointed to your store URL like a normal add to cart form would.
  3. Click the 'Encode HTML' button below the text box, and your encoded version of the code you pasted will be displayed in a text box underneath that button.
  4. Paste the encoded HTML into your page and you're done!

One thing to note here, if you need to make a change to an existing signed link or form, you'll need to rebuild it from scratch without signing.

The How: Implementation Details

A Basic Overview

Whether you use our PHP script or write your own, the basic idea is to sign each and every product option, excluding things that aren't actually “product options” like cart=checkout or output=json and such. There are two steps involved.

First, we concatenate the following values:

  • the product code,
  • the form element's name, (or the name part of the link's name/value pair)
  • and the form element's value (if it's a pre-set value) or the string –OPEN– (if it's an open-input field, like quantity).

Second, we HMAC SHA-256 that string, and append the resulting 64-character hash to the name attribute of the input with double pipes (||) in between. For example, if we had a product with a code of ABC123 and an input with name=“name” value=“My Example Product”, we'd hash the string ABC123nameMy Example Product and append that hash to the name. The resulting html output would look like this:

<!-- Example Form Input -->
<input type="hidden" name="name||753d51d4675bfb6f0aec5e6fbfd8a2e32cbea620c15a181567b052d350469c50" value="My Example Product" />
 
<!-- Example Link -->
<a href="https://example.foxycart.com/cart?name||753d51d4675bfb6f0aec5e6fbfd8a2e32cbea620c15a181567b052d350469c50=My+Example+Product">Add To Cart</a>

(We're going to be using a form as an example, but all of it holds true for links.)

You might realize, however, that this approach wouldn't work for <select> elements, since the values are in the child <option> elements. In this case we append the hash to the value, exactly the same as the above, concatenating:

  • the product code,
  • the form element's name,
  • and the form element's value

Just as above, we append the using double pipes (||), but this time we append it to the value, not the name. Where the hash is appended doesn't really matter; it will work equally well if appended to the name or value. Textarea elements would almost always require it on the name, however.

Bundled Products

As the child product in a bundled product setup often has a different price than if the product were added by itself, we require that the product code used to hash all attributes of the child product contains both the parent code and the child code in the format {childcode}{parentcode} (but without the curly brackets).

For example, if you are adding a T-shirt with the code shirt and a child product with the code poster, the child product attributes would be hashed with the code postershirt, instead of just poster. The parent product's attributes would be hashed like normal with the code shirt.

An Example

Confused? Don't worry! Let's see it in action.

Forms

First we'll take an example form:

<form action="https://your-actual-store-domain.foxycart.com/cart" method="post" accept-charset="utf-8" class="foxycart">
	<input type="hidden" name="name" value="Example T-Shirt" />
	<input type="hidden" name="code" value="abc123" />
	<input type="hidden" name="price" value="25" />
	<select name="size">
		<option value="small{p-2}">Small</option>
		<option value="medium">Medium</option>
		<option value="large{p+3}">Large</option>
	</select>
	<label>Qty: <input type="text" name="quantity" value="" /></label>
	<p><input type="submit" value="Buy It!"></p>
</form>

The important thing to understand with the “form” method is that each and every value that relates to a product must be verified, since each value can impact the price. For example, if we were only to verify the name, code, and price without verifying the size, the size value would be able to modify the price (using a price modifier), thus bypassing our verification. Similarly, each and every value must be only be valid for the correct product (as determined by the code), or a price modifying value for Product A could be applied to Product Z.

In order to generate an HMAC SHA-256 for each input, let's look at what the PHP function would look like (though you can consider this pseudocode if PHP isn't your language of choice):

$secret = "Your store's secret key.";
hash_hmac('sha256', 'abc123nameExample T-Shirt', $secret); // Hash of the "name" input
hash_hmac('sha256', 'abc123price25', $secret); // Hash of the "price" input
hash_hmac('sha256', 'abc123sizesmall{p-2}', $secret); // Hash of the "size" OPTIONS
hash_hmac('sha256', 'abc123sizemedium', $secret); // Hash of the "size" OPTIONS
hash_hmac('sha256', 'abc123sizelarge{p+3}', $secret); // Hash of the "size" OPTIONS

The important pieces are the middle arguments (the abc123nameExample T-Shirt and abc123price25), which is the “message” in the HMAC. Notice that the message is the product code (abc123), the name of the product option, and the value of the product option. They're concatenated (that means “stuck together” in programmer-speak) into one string, and that's what your values are verified against. This HMAC hash is then appended to the value of the input, with a double pipe, || (the “pipe” is the vertical line, generally available by pressing Shift+\). It looks like this:

	<input type="hidden" name="name||f8d3b7b993380dee31ee467984397ed8dc5feec3eb464bc55264cbe33fd691ac" value="Example T-Shirt" />
	<input type="hidden" name="code||8211b9acfbe1ae395dc32bf5ccfa20ab50382d48a65fa3586803288aacbe9ca4" value="abc123" />
	<input type="hidden" name="price||f842ce83aff26e640c1958c3f6f9cba033fbe2a9e53c91b92004d32ee185457c" value="25" />
	<select name="size">
		<option value="small{p-2}||14696b9ff099727a798a5b59d71bc1540a5481adfd957ed2252acf8aec83914a">Small</option>
		<option value="medium||713800d729f987d4609a8b83b60932e64f64690b4c2842b7d6522a62fe514af4">Medium</option>
		<option value="large{p+3}||c8d37d7c32c3c4fc9fe9703e8cc3456020aa9319dd18816d7f887c6f9c616708">Large</option>
	</select>

If you are wanting to hash a link, you hash each individual attribute in much the same way as the forms.

Assuming this is the your link:

https://yourdomain.foxycart.com/cart?name=Black+%26+White+T-Shirt&code=abc123&price=25&size=medium

To hmac sign that link, using the get_verification() helper function from above, you'd do this:

         $atc = "https://yourdomain.foxycart.com/cart";
         $atc .= "?" . get_verification("name", "Black & White T-shirt", "abc123") . "=" . urlencode("Black & White T-shirt");
         $atc .= "&" . get_verification("code", "abc123", "abc123") . "=" . urlencode("abc123");
         $atc .= "&" . get_verification("price", "25", "abc123") . "=" . urlencode("25");
         $atc .= "&" . get_verification("size", "medium", "abc123") . "=" . urlencode("medium");

which would give you a URL that looks like this (broken onto multiple lines for easier reading):

https://yourdomain.foxycart.com/cart
?name||f8d3b7b993380dee31ee467984397ed8dc5feec3eb464bc55264cbe33fd691ac=Black+%26+White+T-Shirt
&code||8211b9acfbe1ae395dc32bf5ccfa20ab50382d48a65fa3586803288aacbe9ca4=abc123
&price||f842ce83aff26e640c1958c3f6f9cba033fbe2a9e53c91b92004d32ee185457c=25
&size||713800d729f987d4609a8b83b60932e64f64690b4c2842b7d6522a62fe514af4=medium

When it comes to URL's, it's important to take note of spaces and special characters within your attribute values. Take note of this line from the above example:

$atc .= "?" . get_verification("name", "Black & White T-shirt", "abc123") . "=" . urlencode("Black & White T-shirt");

While we pass the string to the hashing function with spaces or special characters included (get_verification(“name”, “Black & White T-shirt”, “abc123”)), leaving that in the actual URL would break it both because of the spaces and the ampersand. If you're using PHP, we recommend running the value through PHP's urlencode() function to convert any spaces and special characters to URL friendly equivalents. Note the value you pass off to the hashing function should still be the actual value, not a url encoded version. If you're using a different programming language, you'll need to find an equivalent function within it, or manually convert special characters yourself.

There are also some other special considerations when it comes to spaces and other special characters - please see the 'Important Notes' section below.

User-Editable Inputs

While this method will work for fields that are not user editable and need to be explicitly set, it won't do us much good on fields that require user input, such as a quantity selection, a “Custom Message” textarea for a printed notecard, or a “Registrant Name” field for a conference registration. Because the value is determined by the user in those cases, the hash must be appended to the name field instead of the value field. Further, because the value isn't known at the time of hashing, rather than running the HMAC on the value, we'll use the keyword –OPEN– instead. So the HMAC will look like this:

hash_hmac('sha256', 'abc123quantity--OPEN--', $secret); // Hash of the "quantity" input, allowing for user-generated values

And the HTML will look like this (below). Note that we add ||open after the hash in order for FoxyCart to accept user-generated values in that field.

<label>Qty: <input type="text" name="quantity||753d51d4675bfb6f0aec5e6fbfd8a2e32cbea620c15a181567b052d350469c50||open" value="" /></label>

Multiple Products in One Form

If you're adding multiple products at one time using the numeric prefixes you must generate the hashes without the numeric prefix. So, for example:

<form action="https://yourdomain.foxycart.com/cart" method="post" accept-charset="utf-8" class="foxycart">
	<input type="hidden" name="name" value="Example T-Shirt" />
	<input type="hidden" name="code" value="abc123" />
	<input type="hidden" name="price" value="25" />
	<input type="hidden" name="2:name" value="Baseball Hat" />
	<input type="hidden" name="2:code" value="hat123" />
	<input type="hidden" name="2:price" value="15" />
	<p><input type="submit" value="Buy It! &rarr;"></p>
</form>

You would generate the hash for 2:name without the 2::

hash_hmac('sha256', 'hat123nameBaseball Hat', $secret); // Correct


Note: If no grouping prefix is added (as is the case in the first product in the example above), the option will be assumed to be a 1:. We generally recommend prefixing each product option to prevent any unexpected issues. See this page for more information.

Excluded name/value pairs

There are a few inputs that you don't need to validate, and validating them may cause an error on adding to cart.

Cart options

cart, fcsid, empty, coupon, output, sub_token, redirect, callback, _, locale, template_set

Checkout prepopulation

customer_email, billing_first_name, billing_last_name, billing_address1, billing_address2, billing_city, billing_state, billing_postal_code, billing_country, billing_phone, billing_company, customer_first_name, customer_last_name, customer_address1, customer_address2, customer_city, customer_postal_code, customer_country, customer_phone, customer_company, shipping_first_name, shipping_last_name, shipping_address1, shipping_address2, shipping_city, shipping_state, shipping_region, shipping_postal_code, shipping_country, shipping_phone, shipping_company

Name prefixes

h:, x:, __ (double underscore), utm_ (Google)

Important Notes

  • Before you begin, you must ensure that you have enabled cart validation from your store's admin page ('Store' → 'Advanced' → 'would you like to enable cart validation?').
  • A code value is required to use validation. Without a code there will be nothing to tie options to specific products, thus making the validation potentially vulnerable.
  • Excluded (x: prefixed) and session (h: prefixed) variables are not verified, and should not be hashed.
  • You should not use –OPEN– as the value of a product option, as this will allow the field to be modified.
  • Any curly brackets, { }, are stripped from any OPEN fields, preventing a user-generated value from modifying the price, weight, code, or category.
  • When generating hashes for a link with a value that contains spaces or other special characters, you need to be careful what value you submit to be used in the hash. Within the hash, you would utilise the string with spaces in it such as “My Widget”, but as the value to be included in the link, you would need to encode the spaces to be either %20 or + instead, like =My+Widget. If you're using PHP, we recommend passing the value that you include in the link through urlencode() to encode it for you. So urlencode(“My Black & White Widget”) would become My+Black+%26+White+Widget.

Limitations & Considerations

Validation is All or Nothing

Because it wouldn't be very useful to validate some product options while allowing unvalidated options elsewhere (as any nefarious customer could easily just pass in whatever extra options they wanted to modify the product), validation is all or nothing (with the exception of those name/value pairs noted above). If it's turned on for your store then each and every name/value pair on each and every add-to-cart link and form must be validated.

Big Forms

Forms with many options may push Internet Explorer past its maximum URL length of 2,083 characters. If you have relatively short options (one or two words each) you may be able to get 20+ fields in a normal cart submission using the HMAC method. If you have longer fields (for example, paragraphs of text for a personal message) then the number of fields you are able to submit will be reduced accordingly.

Unfortunately, this is a hard limit in IE versions up to 8 (at the time of this writing; it may very well be the same in IE9+). Fortunately, it only affects GET cart submissions. POST submissions are unaffected. Because most AJAX and JSON(P) cart implementations (including the default FoxyCart cart overlay) rely on serializing form fields to GET in order to bypass cross-domain javascript issues, if you do run into this you may need to take a different approach. While there are a handful of options, including faking a JSONP call by POSTing to a hidden iframe, the easiest option is to eliminate any modal windows and POST to the cart URL directly. If you need assistance please post in our forum.

As always, TEST your forms in Internet Explorer to ensure that your customers don't run into any issues.

Advanced Forms, Option Dependencies

If you have very advanced forms with multiple dependencies and relationships between values (ie. Option A cannot be paired with Option D and requires Option X), that validation must be handled on your end (either before submission to the cart, by validating the request on your end before adding to the cart, and/or validating after the purchase by using the XML datafeed or the API). Because the requirements for those types of situations can vary radically from business to business there would be no solid way to build a “one-size-fits-all” type of solution that would be easier than you building yourself it in the first place.

Tampering by Removing Options: Don't Make Assumptions

It's important to understand that this validation scheme will prevent tampering with existing product options as well as preventing the addition of product options, there is nothing to prevent a user from removing a product option from an add-to-cart form. For this reason we recommend examining how you use product option modifiers if you're modifying the price. It's generally going to be a better idea to start with a set base price that is the upper limit, then adjust the price down using price modifiers rather than starting low and modifying higher.

For example, if you have a conference registration that's $500 for adults and $200 for children, set the price to $500, then on the radio button to select a child's registration adjust it down, like this:

<input type="hidden" name="price" value="500" id="price" /> <!-- Set the base price high -->
 
<input type="radio" name="registration_type" id="registration_type" value="adult" />
<label for="registration_type">Adult Registration, $500</label>
<input type="radio" name="registration_type" id="registration_type" value="child{p-300}" /> <!-- Then adjust down where necessary -->
<label for="registration_type">Child Registration, $200</label>

Site Tools