Request hashing
Generate the order hash and validate response signatures
Hashing
Kashier uses hashing to ensure that the Payment UI and responses shared between your application and Kashier over the network have not been tampered with. We use SHA256 hashing to ensure the safety of transaction data.
The order hash is used to validate your order against what your customers are paying. Order hash generation uses the HMAC SHA256 cryptographic mechanism. You should generate the hash from your backend, as explained below.
You can obtain your API key from the Dashboard.
Both the order hash and the response signature below are generated with your Payment API Key — not your Secret Key. If the "API key" vs. "secret key" vs. "MID" terminology gets confusing, see the credentials-at-a-glance table for how each term maps to its actual header or field name.
// Copy and paste this code into your backend
let crypto = require('crypto');
function generateKashierOrderHash(order) {
const mid = 'MID-123-123'; // Your merchant ID
const CustomerReference = ''; // Required when save, cardToken, or agreement is sent
const amount = order.amount; // e.g., 22.00
const currency = order.currency; // e.g., "EGP"
const orderId = order.merchantOrderId; // e.g., 99
const apiKey = 'yourApiKey';
const path = `/?payment=${mid}.${orderId}.${amount}.${currency}${CustomerReference ? '.' + CustomerReference : ''}`;
const hash = crypto.createHmac('sha256', apiKey).update(path).digest('hex');
return hash;
}
// The result hash for /?payment=mid-0-1.99.20.EGP with key 11111
// should be 606a8a1307d64caf4e2e9bb724738f115a8972c27eccb2a8acd9194c357e4bec// Copy and paste this code into your backend
function generateKashierOrderHash($order) {
$mid = "MID-123-123"; // Your merchant ID
$amount = $order->amount; // e.g., 100
$currency = $order->currency; // e.g., "EGP"
$orderId = $order->merchantOrderId; // e.g., 99
$apiKey = "yourApiKey";
$CustomerReference = ""; // Required when save, cardToken, or agreement is sent
$path = "/?payment=".$mid.".".$orderId.".".$amount.".".$currency;
if (!empty($CustomerReference)) {
$path .= ".".$CustomerReference;
}
return hash_hmac('sha256', $path, $apiKey, false);
}
// The result hash for /?payment=mid-0-1.99.20.EGP with key 11111
// should be 606a8a1307d64caf4e2e9bb724738f115a8972c27eccb2a8acd9194c357e4bec# Copy and paste this code into your backend
import hmac
import hashlib
def generateKashierOrderHash(order):
mid = "MID-123-123" # Your merchant ID
amount = order['amount'] # e.g., 100
currency = order['currency'] # e.g., "EGP"
orderId = order['merchantOrderId'] # e.g., 99
CustomerReference = "" # Required when save, cardToken, or agreement is sent
path = f"/?payment={mid}.{orderId}.{amount}.{currency}"
if CustomerReference:
path += f".{CustomerReference}"
apiKey = "yourApiKey"
return hmac.new(apiKey.encode('utf-8'), path.encode('utf-8'), hashlib.sha256).hexdigest()
# The result hash for /?payment=mid-0-1.99.20.EGP with secret 11111
# should be 606a8a1307d64caf4e2e9bb724738f115a8972c27eccb2a8acd9194c357e4bec// Copy and paste this code into your backend
using System;
using System.Security.Cryptography;
public class Kashier
{
public static string CreateHash()
{
string mid = "mid-0-1"; // Merchant ID from the comment example
string amount = "20"; // Amount from the comment example
string currency = "EGP";
string orderId = "99"; // Order ID from the comment example
string CustomerReference = ""; // Required when save, cardToken, or agreement is sent
string apiKey = "11111"; // Payment API Key from the comment example
string path = $"/?payment={mid}.{orderId}.{amount}.{currency}";
if (!string.IsNullOrEmpty(CustomerReference))
{
path += $".{CustomerReference}";
}
using (var hmac = new HMACSHA256(System.Text.Encoding.ASCII.GetBytes(apiKey)))
{
byte[] hash = hmac.ComputeHash(System.Text.Encoding.ASCII.GetBytes(path));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
}
// The result hash for /?payment=mid-0-1.99.20.EGP with key 11111
// should be: 606a8a1307d64caf4e2e9bb724738f115a8972c27eccb2a8acd9194c357e4becNote
Which string to sign
The algorithm is always the same — HMAC-SHA256, lowercase hex digest, keyed with your
Payment API Key. Only the signed string changes with the operation. Parts are joined
with a literal . and are not URL-encoded.
| Operation | String to sign |
|---|---|
| Payment / checkout, no tokenization | /?payment={mid}.{reference}.{amount}.{currency} |
Payment with tokenization (card.save, card.cardToken, or card.agreement) | /?payment={mid}.{reference}.{amount}.{currency}.{customerReference} |
| Token CRUD — retrieve / delete a saved card | /?tokenization={mid}.{customerReference} |
| 3D Secure pay (resuming an in-flight 3DS order) | /?payment={mid}.{orderId} |
| Payment session create (Apple Pay SDK / sessions) | /?payment={mid}.{reference}.{amount}.{currency} — append .{customerReference} when you send one |
Operations that are not in this table are not hashed. In particular there is no
published signed string for PUT /v3/orders/:orderId — the
refund, void and
capture route — which authenticates with your
Authorization secret key instead. Don't try to reuse the payment string for it.
Here {mid} is your merchant ID, {reference} is your order.reference (your merchant
order ID), and {orderId} on the 3DS-resume row is Kashier's system order ID for
the order being resumed — not your own reference. On the session-create row the amount is
coerced to a number before signing, so 20.00 signs as 20.
customerReference is required for token requests
Append .{customerReference} to the signed string whenever the request carries paymentMethod.card.save, paymentMethod.card.cardToken, or paymentMethod.card.agreement. For those requests Kashier accepts only the customer-reference variant — hash without it and you get 403 INVALID_HASH_CHECK. For a plain payment with none of those fields, both variants are accepted.
Match the key to the mode
The Payment API Key is mode-scoped at the hash layer. A test key validates only on the test- hosts and a live key only on the live hosts, so a mode mismatch fails the hash even when your formula is correct. This is the most common cause of an "invalid hash" error — check the key before you check the string.
Signature
Once the transaction is processed, Kashier creates a signature with response parameters and sends it in the redirection along with other parameters. You need to validate the signature appended to the redirection URL. For validating the signature in the response, use the function explained below.
Kashier signs a fixed, ordered list of parameters — not whatever happens to be on
the URL. Rebuild that exact string yourself rather than iterating the query string, so
that an extra, missing, or re-ordered parameter can't silently break your check. A
parameter that is absent from the redirect is signed as the literal string null.
const crypto = require('crypto');
// Copy and paste this code into your backend
function validateSignature(query, secret) {
const body =
`paymentStatus=${query.paymentStatus}` +
`&cardDataToken=${query.cardDataToken}` +
`&maskedCard=${query.maskedCard}` +
`&merchantOrderId=${query.merchantOrderId}` +
`&orderId=${query.orderId}` +
`&cardBrand=${query.cardBrand}` +
`&orderReference=${query.orderReference}` +
`&transactionId=${query.transactionId}` +
`&amount=${query.amount}` +
`¤cy=${query.currency}`;
const signature = crypto.createHmac('sha256', secret).update(body).digest('hex');
return signature === query.signature;
}<?php
// Copy and paste this code into your backend
$secret = 'your_APIKEY';
$fields = [
'paymentStatus', 'cardDataToken', 'maskedCard', 'merchantOrderId', 'orderId',
'cardBrand', 'orderReference', 'transactionId', 'amount', 'currency',
];
$parts = [];
foreach ($fields as $field) {
$parts[] = $field . '=' . ($_GET[$field] ?? 'null');
}
$body = implode('&', $parts);
$signature = hash_hmac('sha256', $body, $secret, false);
if (hash_equals($signature, $_GET['signature'] ?? '')) {
echo "Success signature";
} else {
echo "Failed signature";
}
?># Copy and paste this code in your backend
import hmac
import hashlib
FIELDS = [
"paymentStatus", "cardDataToken", "maskedCard", "merchantOrderId", "orderId",
"cardBrand", "orderReference", "transactionId", "amount", "currency",
]
def validateSignature(request, secret):
body = "&".join(f"{field}={request.get(field, 'null')}" for field in FIELDS)
signature = hmac.new(
secret.encode("utf-8"), body.encode("utf-8"), hashlib.sha256
).hexdigest()
return "success" if hmac.compare_digest(signature, request.get("signature", "")) else "failure"using System;
using System.Security.Cryptography;
using System.Text;
using System.Web.Mvc;
public class WebhookController : Controller
{
private static readonly string[] Fields = {
"paymentStatus", "cardDataToken", "maskedCard", "merchantOrderId", "orderId",
"cardBrand", "orderReference", "transactionId", "amount", "currency"
};
[HttpPost]
public JsonResult ValidateSignature()
{
string secret = "Payment API Key";
string signature = Request.QueryString["signature"];
if (string.IsNullOrEmpty(signature))
{
return Json(new { success = false, message = "Missing signature." });
}
var parts = new StringBuilder();
foreach (string field in Fields)
{
if (parts.Length > 0) parts.Append("&");
parts.Append($"{field}={Request.QueryString[field] ?? "null"}");
}
byte[] keyBytes = Encoding.ASCII.GetBytes(secret);
byte[] messageBytes = Encoding.ASCII.GetBytes(parts.ToString());
using (var hmac = new HMACSHA256(keyBytes))
{
byte[] hashMessage = hmac.ComputeHash(messageBytes);
string computedSignature = ByteToString(hashMessage).ToLower();
if (computedSignature == signature.ToLower())
{
return Json(new { success = true, message = "Signature validated successfully." });
}
else
{
return Json(new { success = false, message = "Signature validation failed." });
}
}
}
public static string ByteToString(byte[] buff)
{
StringBuilder sbinary = new StringBuilder();
for (int i = 0; i < buff.Length; i++)
{
sbinary.Append(buff[i].ToString("X2")); // hex format
}
return sbinary.ToString();
}
}Handle the webhook: when a payment is successful, Kashier sends a payment webhook event to the webhook URL that you provide. Learn more about using webhooks.
Redirect
Kashier sends the response of the transaction to the redirect URL provided by the merchant. The URL in the format https://your_website.com/redirect can be sent in the data-merchantRedirect parameter in the Payment UI.
Below are the parameters you can include in the Payment UI solution's redirect URL.
Parameters
| Parameter | Description |
|---|---|
| paymentStatus | Status of the transaction: either SUCCESS or FAILURE |
| cardDataToken | Your shopper's card token for future and recurring payments. |
| maskedCard | The masked card of your customer. |
| merchantOrderId | Your order identifier used in order reconciliation. |
| orderId | Kashier's system identifier for the order. |
| cardBrand | The card brand (sometimes called a card network or association). |
| orderReference | The order reference. |
| transactionId | Kashier's identifier for the transaction (for example TX-2498912113), not your own reference. |
| amount | The amount of the order. |
| currency | The currency of the order. |
| signature | Order signature to ensure a secure connection between your server and Kashier. |
| mode | The mode of operation, either test or live. |
Kashier signs a fixed, ordered list of those parameters — not whatever happens to be on the URL. Rebuild the body in exactly this order, then HMAC it with your Payment API Key:
paymentStatus, cardDataToken, maskedCard, merchantOrderId, orderId, cardBrand, orderReference, transactionId, amount, currencysignature and mode are excluded from the signed body. A parameter that is absent from the redirect is signed as the literal string null.
Learn how to validate the signature to ensure that the Payment UI and responses shared between your application and Kashier over the network have not been tampered with.
You can retrieve your order details using order reconciliation. If your transaction fails, you can determine the reason for the failed transaction by mapping the transactionResponseCode to the corresponding payment reason code.
Demo
You can download and install our integration demo: