HMAC - Hash-based Message Authentication Code
HMACs are a way of ensuring that the payload sent to RaiseNow is not manually tampered with before it's processed by RaiseNow. HMACs are generated twice, once on your side, and once on RaiseNow side.
HMACs are computed by selecting specified set of field values from the request and a secret shared between you and RaiseNow. Then, these values are then signed with a secret. The resulting value is the HMAC and is sent together with the original request to RaiseNow. Based on the received values from the request & the shared secret, RaiseNow recomputes the HMAC. If the value from the request and the computed value match, the HMAC is valid. If not, the payload was tampered with and RaiseNow will reject the request.
Step-by-Step Guide
Follow these steps to generate the HMAC and send it to RaiseNow:
- Define a value for the shared secret. It can be up to 64 characters long and must match the regex
[äöüa-zA-Z0-9$&+,:;=?@#|'<>.^*()%!-]{0,64}
- Define which parameters should be included in the HMAC calculation. E.g.
amount.currency
- Define for how long you want each HMAC to be considered valid. Typically, you'd define 30 minutes as a starting point
- Send the values from 1-3 to RaiseNow
- Then, for creating a payment, payment source or payment agreement, generate the HMAC yourself, by
- Taking all the parameters from step 2 and sorting them by path in alphabetical ascending order (e.g.
a.b
comes beforeb.a
) - Taking your secret from step 1
- Take your HMAC library of your choice, and use
sha256
as the algorithm to generate it - Take the unix timestamp of when you generated the HMAC
- Taking all the parameters from step 2 and sorting them by path in alphabetical ascending order (e.g.
- Pass the HMAC as a string hexit value and the unix timestamp as an integer in the request:
{
"hmac": {
"timestamp: 1748936579,
"value": "174bbd25de2bbf6e6bc82a406d4c6d29bb41a534616dca0b6869817d77e00f35"
}
} - Send the request to RaiseNow
Examples
You can use the following example as a starting point to generate the HMAC on your side:
- PHP
- NodeJS
<?php
$secret = 'my top secret value';
$algo = 'sha256';
// the parameters to hash
$payload = [
'amount.value' => 1000,
'amount.currency' => 'EUR',
'test_mode' => true,
'custom_parameters.b_key' => 'b_value',
'custom_parameters.a_key' => 'a_value'
];
// sort by path: alphabetically, in ascending order
ksort($payload);
// properly serialise boolean values into their string equivalents
foreach ($payload as $key => $value) {
if (is_bool($value)) {
$payload[$key] = $value ? 'true' : 'false';
}
}
$payload = implode('', $payload);
echo "expected HMAC: 4df1cbf05c7a9c375127f466d6c54b7bdb64e94f46e6ae1975bb71d67a6fcf66\n";
echo "actual HMAC: " . hash_hmac($algo,$payload, $secret, false);
#!/usr/bin/env node
import {createHmac} from 'crypto'
const secret = 'my top secret value'
const algo = 'SHA-256'
var payload = {
'amount.value': 1000,
'amount.currency': 'EUR',
'test_mode': true,
'custom_parameters.b_key': 'b_value',
'custom_parameters.a_key': 'a_value'
};
// sort by path: alphabetically, in ascending order
const orderedKeys = Object
.keys(payload)
.sort()
// concatenate values and replace booleans with string equivalents
let orderedPayload = ''
orderedKeys.forEach(key => {
let value = payload[key]
if (typeof payload[key] == "boolean") {
value = payload[key] === true ? 'true' : 'false'
}
orderedPayload += value
})
console.log(orderedPayload)
const hmac = createHmac('SHA-256', secret)
.update(orderedPayload)
.digest('hex')
console.log('Expected HMAC: 4df1cbf05c7a9c375127f466d6c54b7bdb64e94f46e6ae1975bb71d67a6fcf66')
console.log('Generated HMAC: ' + hmac)