Signing requests

Signing requests

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:
Header
Value
x-woosa-domain
your shop domain, e.g. myshop.example.com
x-woosa-signature
the signature you compute below
x-woosa-signature-type
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:
  • uri: 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.
  • body: the exact raw content of the request body you are sending. For GET requests, which have no body, use an empty string "".
  • time: 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=

PHP example

$data = [
'uri' => $uri, // e.g. "/woocommerce/adyen/stores" (path + query, starts with /)
'body' => $body, // the exact JSON string you send; "" for GET
'time' => time(),
];

$dataSignature = base64_encode(hash_hmac('sha256', json_encode($data), $secret, true)); // $secret = your woosa_secret
$finalSignature = sprintf('%s.%s', $time, $dataSignature); // send $finalSignature in the x-woosa-signature header


JavaScript / Node.js example (mind the slashes)

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 data = {
uri: uri, // path + query, starts with /
body: body, // exact request body string; "" for GET
time: time,
};

// JSON.stringify does NOT escape "/". Replace manually to match PHP's json_encode.
const dataToSign = JSON.stringify(data).replace(/\//g, '\\/');

const dataSignature = crypto
.createHmac('sha256', secret) // secret = your woosa_secret
.update(dataToSign)
.digest('base64');

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.