Generating x-pch-digest Header for Online Ordering APIs
The x-pch-digest is a header that must be included with most Punchh online ordering API calls. The x-pch-digest header adds another layer of security to the API calls between your application and the Punchh server by verifying that the encrypted signature sent to Punchh matches the expected hash based on your application's secret key.
Online Ordering vs. Mobile APIs
The x-pch-digest header is required for the online ordering APIs and the mobile APIs. The header is generated in the same way, but there is a difference. The online ordering APIs use SHA1 encryption, while the mobile APIs use SHA256 encryption, so remember to encrypt your hash accordingly depending on the API you are targeting. This topic is focused on the online ordering APIs. Refer to the mobile API topic if you are generating the header for those APIs.
Syntax
The x-pch-digest header is created by concatenating the request URL and the request body and creating a SHA1 hash with your client secret.
hmac("sha1", concat(uri, body), secret)
| Field | Description |
|---|---|
| URI | The URI should match the request URI, minus the base URI. |
| Body | The body should match the request body exactly as it is sent, with white space removed. |
For example:
- You are making a call to
https://SERVER_NAME_GOES_HERE.punchh.com/api/auth/checkins/balance. The URI in this case would be:/api/auth/checkins/balance - You are sending the following request body:
{"client":"CLIENT_GOES_HERE"} - Therefore, you should concatenate the following payload:
/api/auth/checkins/balance{"client":"CLIENT_GOES_HERE"} - Creating a SHA1 hash with this payload and your client secret will generate the same hash that the Punchh server would generate when it receives your API call, thereby passing the security check.
Common Issues
If you receive a 412 error from the API, the issue is that the x-pch-digest header you are sending does not match what the Punchh server is generating. Here are some common things to check:
- Make sure that the request URI you are concatenating starts with a
/and does not contain the base URI, such as:https://SERVER_NAME_GOES_HERE.punchh.com - Make sure that the request body you are concatenating matches exactly what you are sending in the API call and does not include extra white space for indentation.
- If the API call is a GET request that includes a request body, some API libraries will ignore any body parameters, so the request body you are sending will actually be null. For example, JavaScript libraries such as
XMLHttpRequest,fetch, andaxioswill disregard GET bodies. If this is the case for you, send the body parameters as query string parameters instead.
For the previous example, your request URI would change to/api/auth/checkins/balance?client=CLIENT_GOES_HEREand the body would be null, so you would end up creating a hash with/api/auth/checkins/balance?client=CLIENT_GOES_HEREand your client secret.
Code Examples
JavaScript
// You will need to install crypto-js before this code will work. To do so, run this at a command prompt inside your project directory:
// npm install crypto-js
function generateSignature() {
var CryptoJS = require("crypto-js");
const key = "SECRET_GOES_HERE";
// Make sure this path is the path from the endpoint you are using:
const urlPath = '/api/auth/customers.json';
// Request body example for a 'Sign up with email and password' (/api/auth/customers.json) API call
const requestBody = JSON.stringify({ client: "CLIENT_ID_GOES_HERE", user: { email: "test@example.com", password: "PASSWORD_GOES_HERE", password_confirmation: "PASSWORD_GOES_HERE", first_name: "FIRST_NAME_GOES_HERE", last_name: "LAST_NAME_GOES_HERE", terms_and_conditions: true, zip_code: "98701", phone: "1111111111", birthday: "1985-10-26" } });
const payload = urlPath + requestBody;
const signature = CryptoJS.HmacSHA1(payload, key).toString();
console.log("Signature: " + signature);
};
generateSignature();
Ruby
require 'json'
require 'net/http'
require 'openssl'
require 'uri'
def create_request(uri)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
# Make sure the HTTP request method here matches the type of request you are making (GET, POST, PATCH, etc.):
req = Net::HTTP::Post.new(uri)
return req
end
def generate_signature
key = "SECRET_GOES_HERE"
# Make sure this URL is the URL for the endpoint you are using:
url = "https://SERVER_NAME_GOES_HERE.punchh.com/api/auth/customers.json"
uri = URI("#{url}")
request = create_request(uri)
# Request body example for a 'Sign up with email and password' (/api/auth/customers.json) API call
request.body = {client:"CLIENT_ID_GOES_HERE",user: {email:"test@example.com",password:"PASSWORD_GOES_HERE",password_confirmation:"PASSWORD_GOES_HERE",first_name:"FIRST_NAME_GOES_HERE",last_name:"LAST_NAME_GOES_HERE",terms_and_conditions:true,zip_code:"98701",phone:"1111111111",birthday:"1985-10-26"}}.to_json
payload = uri.path + request.body
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), key, payload)
puts "Signature: " + signature
end
generate_signature
Java
import java.security.SignatureException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Formatter;
public class Signature {
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
private static String toHexString(byte[] bytes) {
Formatter formatter = new Formatter();
for (byte b : bytes) {
formatter.format("%02x", b);
}
return formatter.toString();
}
public static void main(String args[])throws Exception{
String secret = SECRET_KEY_GOES_HERE;
// Make sure this path is the path from the endpoint you are using:
String uriPath = "/api/auth/customers.json";
String requestBody = "{\"client\":\"CLIENT_ID_GOES_HERE\",\"user\": {\"email\": \"test@example.com\",\"password\": \"PASSWORD_GOES_HERE\"}}";
String data = uriPath + requestBody;
// Get an hmac_sha1 key from the raw key bytes
SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM);
// Get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
// Compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(data.getBytes());
System.out.println(toHexString(rawHmac));
}
}
PHP
PHP
<?php
$host = 'https://SERVER_NAME_GOES_HERE.punchh.com/api/auth/customers/sign_in.json'; // Login API end point
$client = 'CLIENT_GOES_HERE'; // Client ID of business to be used to make API calls
$secret = 'SECRET_GOES_HERE'; // Secret of business to be used to make API calls
$body = '{"client": "CLIENT_GOES_HERE","user":{"email": "test@example.com","password": "PASSWORD_GOES_HERE"}}'; // Body in JSON format
$parsed_host = parse_url($host);
$url_path = $parsed_host['path']; // Path of URL: /api/auth/customers/sign_in.json
$x_pch_digest = hash_hmac('sha1', $url_path.$body, $secret); // Signature specific to every request using SHA1 which is to be passed in `x-pch-digest` key in header
Python
import hmac
import hashlib
def generate_signature():
secret = 'SECRET_GOES_HERE'
# Make sure this path is the path from the endpoint you are using:
path = '/api/auth/customers.json'
# Request body example for a 'Sign up with email and password' (/api/auth/customers.json) API call
request_body = json.dumps({
"client": "CLIENT_GOES_HERE",
"user": {
"email": "test@example.com",
"password": "PASSWORD_GOES_HERE",
"password_confirmation": "PASSWORD_GOES_HERE",
"first_name": "FIRST_NAME_GOES_HERE",
"last_name": "LAST_NAME_GOES_HERE",
"terms_and_conditions": True,
"zip_code": "98701",
"phone": "1111111111",
"birthday": "1985-10-26"
}
})
payload = ''.join((path,(request_body)))
signature = hmac.new(bytes(secret, 'UTF-8'), bytes(payload, 'UTF-8'), hashlib.sha1).hexdigest()
print(signature)
generate_signature()