From now on, almost every request must carry a signature. Think of a signature as a tamper-proof wax seal. It proves two things to Woosa: that the request really came from you, and that nobody changed it in transit. It is recomputed fresh for every single request.
You will send three headers on signed requests:
your shop domain, e.g. myshop.example.com
the signature you compute below
plugin (this is the documented default)
You put three pieces of information into a small package, scramble it with your secret using a standard one-way function (HMAC-SHA256), and stick a timestamp on the front. The three pieces are:
- the request path of Woosa API endpoint you are going to call plus any query string, always starting with
/. Example: /woocommerce/adyen/stores/ST32.../payments. - the exact raw content of the request body you are sending. For GET requests, which have no body, use an empty string
"". - the current time as a Unix timestamp (seconds since 1970).
Then:
Build a JSON object { "uri": ..., "body": ..., "time": ... }.
Turn it into HMAC-SHA256 using your woosa_secret as the key, and Base64-encode the result. That is your dataSignature.
The final header value is time + a dot + dataSignature.
The end result looks like this:
1636117273.MNw1Rd5O0evUmwXy85j0ca2bg8SDg/Xm4WfA3LdI5gg=
$dataSignature = base64_encode(hash_hmac('sha256', json_encode($data), $secret, true));
$finalSignature = sprintf('%s.%s', $time, $dataSignature);
There is one subtle trap. PHP's json_encode escapes forward slashes (it writes \/ instead of /), and JavaScript's JSON.stringify does not. If your signature is generated in JavaScript, you must add that escaping by hand, otherwise the two sides compute different signatures and every request fails with 401.
const crypto = require('crypto');
const time = Math.floor(Date.now() / 1000);
const dataToSign = JSON.stringify(data).replace(/\//g, '\\/');
const dataSignature = crypto
.createHmac('sha256', secret)
const finalSignature = `${time}.${dataSignature}`;
The number one cause of "it says 401 and I do not know why": the string you signed does not exactly match the request you sent. The body you sign must be byte-for-byte the same text you put in the request. The uri you sign must include the query string and start with /. And in JavaScript, remember the slash escaping.