AgentCASH.js – Callback Signature¶
General¶
In order to prevent faked backend callbacks, each callback originating from the AgentCASH backend will be extended with two security fields: signature_order
and signature
.
Example callback:
{
"amount": "30.01",
"approval_code": "111222",
"card_brand": "mastercard",
"card_cardholder_name": "Bob Gordon",
"card_fingerprint": "2d597977-880d-4db8-92f0-47799439af72",
"card_masked_pan": "550000******0012",
"created_at": "2016-09-14T14:01:02Z",
"currency": "EUR",
"external_id": "ID-654321"
"payment_id": "c2efcaf2-e222-405c-b9d4-6f9932d07f76",
"receipt_url": "http://test.host/i/vXMM9letRr2NJ_Ej4K2e6w",
"status": "approved",
"type": "purchase",
"signature_order": "payment_id,external_id,type,status,receipt_url,amount,currency,approval_code,card_brand,card_masked_pan,card_cardholder_name,card_fingerprint,created_at,signature_order,secret",
"signature": "5884f2d86237c507ddd62cfcbc2c032020f45c362f31eb00a99f83205bbfe06a65fb427cd8f00f38cfdf812ca2235b5dce76ec8ef92578e47d9b8d2996655f64"
}
The signature is a SHA-512 hash of all object values ordered and joined (without delimiters) as signature_order
specifies. A secret value is also included and the hash is calculated for the final string.
The above example intentionally shows fields sorted in the alphabetical order to demonstrate that their order for signature calculation is in fact specified by the signature_order
field.
Secret from the example is "MeetTheFlintstones"
.
Therefore, values ordered according to the signature_order
field from the example are:
[
"c2efcaf2-e222-405c-b9d4-6f9932d07f76", # payment_id
"ID-654321", # external_id
"purchase", # type
"approved", # status
"http://test.host/i/vXMM9letRr2NJ_Ej4K2e6w", # receipt_url
"30.01", # amount
"EUR", # currency
"111222", # approval_code
"mastercard", # card_brand
"550000******0012", # card_masked_pan
"Bob Gordon", # card_cardholder_name
"2d597977-880d-4db8-92f0-47799439af72", # card_fingerprint
"2016-09-14T14:01:02Z", # created_at
"<see-above>", # signature_order
"MeetTheFlintstones" # secret
]
Array values are joined (without delimiters) into a string, hashed and the signature becomes:
"5884f2d86237c507ddd62cfcbc2c032020f45c362f31eb00a99f83205bbfe06a65fb427cd8f00f38cfdf812ca2235b5dce76ec8ef92578e47d9b8d2996655f64"
If the calculated signature matches the one from the request, it can be trusted. Otherwise it may not originate from a trusted source.
Secret¶
The secret is coupled with the API key used to identify the website and it can be retrieved from the AgentCASH admin UI (Webshop > API). Only merchant admins have access to this data.
The secret should not be shared publicly with anyone.
Signature validation example¶
The following pseudocode illustrates the signature validation algorithm:
secret = ENV["AGENTCASH_SECRET"] # keep in an env-var or config
json = JSON.parse(request.body) # parse callback data
if (json["signature"]) {
# signature is present, need to validate it first
signature_fields = json["signature_order"].split(",")
# append field values in the correct order
values = []
for (field in signature_fields) {
if (field == "secret") {
values.add(secret) # add the secret
} else {
values.add(json[field]) # add the field value
}
}
# calculate SHA-512 hash from the values
values_string = values.join("")
calculated_signature = sha512_digest(values_string)
if (json["signature"] == calculated_signature) {
# ok - we can proceed
} else {
# error - invalid signature
}
} else {
# error - missing signature
}