To protect your user’s privacy and be compliant with US and International Privacy laws, the following precautions must be taken to protect requests between Groupon and you. This is especially true in cases where Groupon sends you PII (personally identifiable information) data.
The following safeguards are expected to be in place between Groupon and you:
Requests are signed in a manner similar to OAuth 1.0a HMAC-SHA1. However, because OAuth is not used, there are some differences. The hash-based message algorithm used is HMAC-SHA1. The following is a high-level description about how the signature-generation process works. Each server-to-server request must be digitally signed.
Note: The previous version of the algorithm (v1.0) is currently deprecated.
An example of the entire algorithm (v1.1) to generate the signature string is provided below:
PERCENT_ENCODE(
BASE64(
HMAC_SHA1(
signing_key,
uppercase_http_verb + "&" +
PERCENT_ENCODE(nonce) + "&" +
PERCENT_ENCODE(base_url) + "&" +
PERCENT_ENCODE(parameter_string) + "&" +
SHA256_AS_HEX(
TRIM(
request_body
)
)
)
)
)
An example of a signed request based on the steps described is provided below:
POST
https://groupon.example.com/groupon/v1/products/00000000-0000-00ff-ffff-ffffffffffff/availability?purchaserId=ffffffff-ffff-ffff-0000-000000000000&locale=en-US&foo=Hello+World
HTTP/1.1
Authorization: groupon-third-party version="1.0",digest="HMAC-SHA1",nonce="2e9724ca18a74b349ffa65d17611e5b0",signature="1W73F6F0eDNAFqP59YDNtTxhkDg%3D"
{
"attributes": {
"startAt": "2015-12-01T06:59:59Z",
"endAt": "2015-12-01T06:59:59Z"
}
}
The step-by-step process for generating an HMAC-SHA1 signature for a request is provided below.
Parse the URI for the request into the following components:
HTTP Verb
POST
Base URL
https://groupon.example.com/groupon/v1/products/00000000-0000-00ff-ffff-ffffffffffff/availability
Nonce
2e9724ca18a74b349ffa65d17611e5b0
Query String
purchaserId=ffffffff-ffff-ffff-0000-000000000000&locale=en-US&foo=Hello+World
Request Body
{
"attributes": {
"startAt": "2015-12-01T06:59:59Z",
"endAt": "2015-12-01T06:59:59Z"
}
}
\n
To create the parameter string, process the keys and values from the query string to ensure they are handled the same on all frameworks, which might reorder the values from the query string.
The content type of requests is not “application/x-www-form-urlencoded” and the JSON request body is handled separately, so the only concern is with query string parameters and not the request body.
The parameters should be handled as follows:
Handling the parameters in this manner results in the following parameter string:
foo=Hello%2BWorld&locale=en-US&purchaserId=ffffffff-ffff-ffff-0000-000000000000
Please be aware that some URL parsers and frameworks will convert a plus (+) character into a space character, which can affect your resulting parameter string. The plus (+) character should be maintained so that it percent-encodes into %2B.
Instead of including the entire request body in the HMAC, create a hash of the request body. This allows Groupon and its partners to better isolate systems dealing with a request body, as a request body may contain PCI (payment card industry) or personal data. Using a hash still allows verification of the signature against the request body, but does not require the code or system doing the validation to have access to the raw request body (that is, the hash could be passed into the validating code or system to validate, instead of the raw request).
First, the request body should be trimmed to strip out any leading or trailing whitespace. In our example, the trailing newline is removed:
{
"attributes": {
"startAt": "2015-12-01T06:59:59Z",
"endAt": "2015-12-01T06:59:59Z"
}
}
Then, the SHA256 hash needs to be calculated from the trimmed request body:
891e8dc452cd14702978d1ededb4445c18974bfae0c027ec8a1ade96d3a64395
An empty encoded request body will result in the SHA256 hash value for an empty string. (Specifically, this is defined as: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855)
This is also true with a GET request; treat the lack of a body in the GET request the same as you would an empty body in any other request type.
Each of the values should be percent-encoded to avoid conflicts with the delimiter in the final signature base.
Base URL
https%3A%2F%2Fgroupon.example.com%2Fgroupon%2Fv1%2Fproducts%2F00000000-0000-00ff-ffff-ffffffffffff%2Favailability
Nonce
2e9724ca18a74b349ffa65d17611e5b0
Parameter String
foo%3DHello%252BWorld%26locale%3Den-US%26purchaserId%3Dffffffff-ffff-ffff-0000-000000000000
As mentioned above, the request body should be normalized to remove leading and trailing whitespace which can result in an entirely empty body. If a request body value is empty, then the resulting encoded value would be an empty string.
Append each encoded value with the ampersand character (&) into a single string. The order should be:
POST&2e9724ca18a74b349ffa65d17611e5b0&https%3A%2F%2Fgroupon.example.com%2Fgroupon%2Fv1%2Fproducts%2F00000000-0000-00ff-ffff-ffffffffffff%2Favailability&foo%3DHello%252BWorld%26locale%3Den-US%26purchaserId%3Dffffffff-ffff-ffff-0000-000000000000&891e8dc452cd14702978d1ededb4445c18974bfae0c027ec8a1ade96d3a64395
To create the signature for a request, you will obtain a unique signing key (the “API Key”) from the Groupon Partner Management Tool. This signing key acts as a shared secret and is used in the HMAC to generate the signature.
A testing key is generated for testing your integrations, and a separate production key is generated when Groupon has approved an integration, and that integration is ready to be launched.
Obtain the signing key for you and Groupon to share. This key will be used in the HMAC-SHA1 digest function.
Using a signing key of "secret-code", our signature base string is hashed to the following bytes:
103, 92, 144, 130, 107, 145, 27, 41, 45, 89, 121, 114, 60, 214, 39, 154, 107, 119, 228, 101
These bytes should then be Base64 encoded:
Z1yQgmuRGyktWXlyPNYnmmt35GU=
The Base64 encoded value should then be percent encoded to create the signature string:
Z1yQgmuRGyktWXlyPNYnmmt35GU%3D
Groupon will specify the signature in the Authorization header. The authorization scheme is “groupon-third-party” and the credentials value of the header will contain several pieces of information.
Each piece of information is in a comma-delimited string to separate the key/value pairs, and each key is separated from the value by an equals character (=). The value is in quotes.
Authorization: groupon-third-party version="1.1",digest="HMAC-SHA1",nonce="2e9724ca18a74b349ffa65d17611e5b0",signature="Z1yQgmuRGyktWXlyPNYnmmt35GU%3D"
You should run through the same algorithm as Groupon on the inbound request and then compare the result against the signature value in the Authorization header. If the values match, the request should be processed normally.
If the values do not match, then you should generate a 401 Response with the following body:
{
"errors": [
{
"code": "INVALID_REQUEST_SIGNATURE"
}
],
"httpCode": 401
}
Download Code from here (zip)
BY USING THE SAMPLE CODES PROVIDED ON THIS WEBSITE YOU ACKNOWLEDGE AND AGREE THAT YOU DO SO AT YOUR OWN RISK. NEITHER GROUPON, NOR ITS SUBSIDIARIES OR AFFILIATES OR ANY OF THEIR RESPECTIVE EMPLOYEES, AGENTS, MERCHANTS, THIRD-PARTY CONTENT PROVIDERS OR LICENSORS OR ANY OF THEIR OFFICERS, DIRECTORS, EMPLOYEES OR AGENTS, WARRANT THAT USE OF THE SAMPLE CODES WILL BE SECURE, VIRUS-FREE, OR ERROR FREE, NOR DO THEY MAKE ANY WARRANTY AS TO THE RESULTS THAT MAY BE OBTAINED FROM USE OF THE SAMPLE CODES. THE SAMPLE CODES ARE MADE ACCESSIBLE OR AVAILABLE ON AN “AS IS” AND “AS AVAILABLE” BASIS. TO THE EXTENT ALLOWED BY APPLICABLE LAW, GROUPON HEREBY DISCLAIMS ANY AND ALL REPRESENTATIONS, WARRANTIES, AND CONDITIONS, WHETHER EXPRESS, IMPLIED, OR STATUTORY, AS TO THE OPERATION OF THE SAMPLE CODES
If you employ global user identification (for example, to enforce purchase quantities across multiple distribution channels), you might have an existing account or purchase history for a given email address. Groupon can send a hashed version of the user (purchaser) email to you to support your ability to look up existing information about the user. This information can be used in the checkAvailability call to enforce purchase quantity limits for a user, across your own system, and any other distribution channels you may have. It can also be used on the reserve call to associate the purchase with an existing user account, or to fail the request if that user has exceeded purchase limits.
The email address is normalized by removing any leading or trailing whitespace and converting all characters to lowercase. The email address will then be hashed using SHA-256. Because the email address will be passed as a URL query parameter, the result of the SHA-256 hash will be Base64 URL safe encoded without padding (that is, no = characters). For example, the email address "GROUPon@groupON.com" will result in the hash "pOylLJ4I9B-O7O7LUEFgCjWK8ZVPLs0peR0ByN9Nl84".
By providing the hashed email address and the Groupon algorithm for hashing email addresses, you can apply the same algorithm to user email addresses already in your system. When a hashed purchaser email address from Groupon matches a hashed user email address in your system, you can match the Groupon purchase with an existing user in your system.
Groupon recommends that you store the Base64 encoded value with the user’s account and/or purchase information to make requests quicker for the Check Availability and Reserve endpoints for SLA reasons. Obviously, this practice would require a backfill of existing data as well as a change to calculate and store this information upon new account or reservation creation outside of Groupon transactions.
Please note that a Groupon user is able to change their email address, which will result in a different purchaserEmailHash being sent to you even though the Groupon user is the same. However, the purchaserId will remain the same even if the email address is changed. You may choose to use both the purchaserEmailHash and the purchaserId to reliably enforce user limits even if the user changes their email address by looking for a match of either purchaserEmailHash or purchaserId. The implementation of this logic is up to your discretion.
Note that the hashed email is for the user who is signed in to Groupon. If the user makes a purchase and identifies a different email for the recipient, that recipient email is not captured. Also, if a user makes a purchase using the Guest Checkout option, no hashed email information is available.
An overview of the entire algorithm to generate the email hash is provided below:
BASE64_URLSAFE(
SHA_256(
TO_BYTES_UTF8(
TRIM(
LOWERCASE(
email_address
)
)
)
)
)
Groupon generates a customer service ID that is sent to you in the Reserve and Fulfill requests. Groupon expects you to send back your customer service ID in the response. This customer service ID is what Groupon provides to the user in the user's confirmation email. You should store the mapping between the Groupon customer service ID and any ID that you use to identify a particular order.
In case your customer service wants to sync with Groupon customer service and trigger any refunds, then Groupon customer service IDs should be used, as the Groupon customer service team is familiar with Groupon IDs.
You must follow these technical requirements when integrating with Groupon.
Groupon does not support asynchronous polling for updates to bookings. If the partner returns a transitional status (i.e. “fulfilling” or “cancelling”) as the result of a synchronous call, Groupon will not poll for updates. You should inform Groupon when that the state changes. All updates must be synchronous during the booking experience and subsequent updates are communicated with over Webhook.
Groupon has two general styles of integrations; standard and custom. Standard integrations leverage a standardized user experience and common internal platform. Custom integrations due to the industry vertical/merchants that they serve tend to have a customized user experience. In order to enable custom experiences Groupon leverages a customized iframe implementation where that iframe is either owned by Groupon or the partner. At all times this must adhere to Groupons design styles. Whenever iframe is mentioned this strictly relates to custom integrations, whereas non-iframe integrations are standard integrations.
As the iframe is owned by the partner there are many aspects of pricing during the user experience Groupon is blind to. If pricing on a product can change between calls, the partner is responsible for ensuring that the price returned in checkAvailability is still applicable on reserve. This window for pricing changes is larger for iframe integrations since the purchaser first sees the price on the iframe before being sent to the checkout page.
The mechanism for validating the price is the same for iframe and non-iframe booking. If the partner does not have pricing changes, they needn’t implement anything for handling price changes. If the partner does not implement support, Groupon will charge the user the price returned in priceSummary from the checkAvailability response. Iframe partners use pre-reservation ID to ensure that pricing information is still applicable between checkAvailability, and reserve, whereas standard (non-iframe) partners will have the prices compared directly. Regardless of integration type pricing must be the same between the check availability and reserve calls. Pricing must be locked in during reserve since the purchaser is charged afterward, which means that fulfill will not have a pricing change.
Since the pre-reservation ID will be passed back to the partner in subsequent calls, the partner can ensure that the pricing associated with the pre-reservation ID is still valid. If a partner is not implementing an iframe, they can pass the pre-reservation ID back on the checkAvailability response and Groupon will pass that back in the reserve call.
If pricing has changed during the checkAvailability call, the partner should return the prereservationPriceSummary object in the response which includes the original price. The updated price will be returned as normal in the priceSummary object. Please refer to the checkAvailability section for further details. Please note that only the price that the purchaser pays can be considered a price change; if the value of the product being purchased changes, that should be treated as the product no longer being available.
If the pricing has changed during the reserve call, then this should be treated as an error and the appropriate error code should be returned. Please refer to the reserve error response section for further details.
As an extra level of protection, Groupon will pass the price it expects should be charged as a priceSummary in the reserve request. The partner should verify that this price is expected and return an error response if the price does not match expectations.
Similarly, the partner must return the price it expected to be charged in the reservation for all responses where a reservation is returned. During the reserve call, Groupon will compare the price it expected to be charged to the price returned in the response. If the price does not match the expected price, Groupon may choose to cancel the reservation to avoid an incorrect charge to the customer.
The presence of priceSummary in the request and response is required for both iframe and not iframe offers to ensure the price charged is accurate.