Generating x-pch-digest Header for Mobile APIs
The x-pch-digest is a header that must be included with most Punchh mobile 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 mobile APIs. Refer to the online ordering 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 SHA256 hash with your client secret.
hmac("sha256", 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/api2/mobile/users/profile. The URI in this case would be:/api2/mobile/users/profile - You are sending the following request body:
{"client":"CLIENT_GOES_HERE"} - Therefore, you should concatenate the following payload:
/api2/mobile/users/profile{"client":"CLIENT_GOES_HERE"} - Creating a SHA256 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 is not matching 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 ashttps://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/api2/mobile/users/profile?client=CLIENT_GOES_HEREand the body would be null, so you would end up creating a hash with/api2/mobile/users/profile?client=CLIENT_GOES_HEREand your client secret.
Code Examples
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/api2/mobile/users"
uri = URI("#{url}")
request = create_request(uri)
# Request body example for a 'Sign Up / Register' (/api2/mobile/users) 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('sha256'), key, payload)
puts "Signature: " + signature
end
generate_signature
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 = '/api2/mobile/users';
// Request body example for a 'Sign Up / Register' (/api2/mobile/users) 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.HmacSHA256(payload, key).toString();
console.log("Signature: " + signature);
};
generateSignature();
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_SHA256_ALGORITHM = "HmacSHA256";
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 = "/api2/users/login";
String requestBody = "{\"client\": \"CLIENT_ID_GOES_HERE\", \"user\": {\"email\": \"test@example.com\",\"password\": \"PASSWORD_GOES_HERE\"}}";
String data = uriPath + requestBody;
// Get an hmac_sha256 key from the raw key bytes
SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA256_ALGORITHM);
// Get an hmac_sha256 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance(HMAC_SHA56_ALGORITHM);
mac.init(signingKey);
// Compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(data.getBytes());
System.out.println(toHexString(rawHmac));
}
}
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 / Register' (/api2/mobile/users) 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.sha256).hexdigest()
print(signature)
generate_signature()