Miva uses an HMAC signature to check that a request was made by someone holding your signing key and that the body wasn’t changed in transit. You calculate the signature from the exact JSON body you send, then put it in the X-Miva-API-Authorization header next to your access token.
Use HMAC-SHA256. Miva also accepts HMAC-SHA1 for older integrations; Authentication and security covers it.
You need a token with the signature requirement on (SHA256), its access token and its signing key. See Create an API token.
How a signature is built#
1. Decode the signing key
Base64-decode the signing key from the admin. The decoded bytes are the HMAC key.
2. Build the JSON body
Serialize the request to a string once. These exact bytes get signed and sent.
3. Calculate HMAC-SHA256
Run HMAC-SHA256 over the body with the decoded key. Base64-encode the binary digest.
4. Build the header
X-Miva-API-Authorization: MIVA-HMAC-SHA256 TOKEN:SIGNATURE
5. Send the request
POST the same body to /mm5/json.mvc with the header.
Example#
Each example sends one signed ProductList_Load_Query request and prints the response. Replace the endpoint, token, signing key and store code with your own.
<?php
$apiToken = 'YOUR_API_TOKEN';
$signingKey = 'YOUR_BASE64_SIGNING_KEY';
$body = json_encode([
'Store_Code' => 'YOUR_STORE_CODE',
'Miva_Request_Timestamp' => time(),
'Function' => 'ProductList_Load_Query',
'Count' => 1,
'Offset' => 0
], JSON_UNESCAPED_SLASHES);
if ($body === false) {
throw new RuntimeException('JSON encoding failed');
}
// Decode the signing key copied from the admin.
$key = base64_decode($signingKey, true);
if ($key === false) {
throw new RuntimeException('Invalid signing key');
}
// Sign the exact JSON body being sent. true returns the raw binary digest.
$digest = hash_hmac('sha256', $body, $key, true);
$signature = base64_encode($digest);
$authorization = 'MIVA-HMAC-SHA256 ' . $apiToken . ':' . $signature;
$curl = curl_init('https://www.example.com/mm5/json.mvc');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Miva-API-Authorization: ' . $authorization
],
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($curl);
if ($response === false) {
throw new RuntimeException(curl_error($curl));
}
curl_close($curl);
echo $response;
Save as signed-request.mjs and run with Node.js 18 or later.
import crypto from 'node:crypto';
const apiToken = 'YOUR_API_TOKEN';
const signingKey = 'YOUR_BASE64_SIGNING_KEY';
const body = JSON.stringify({
Store_Code: 'YOUR_STORE_CODE',
Miva_Request_Timestamp: Math.floor(Date.now() / 1000),
Function: 'ProductList_Load_Query',
Count: 1,
Offset: 0
});
// Decode the signing key, then sign the exact body being sent.
const key = Buffer.from(signingKey, 'base64');
const signature = crypto.createHmac('sha256', key).update(body).digest('base64');
const response = await fetch('https://www.example.com/mm5/json.mvc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Miva-API-Authorization': `MIVA-HMAC-SHA256 ${apiToken}:${signature}`
},
body
});
console.log(await response.text());
Uses only the standard library. Runs on Python 3.8 or later.
import base64
import hashlib
import hmac
import json
import time
import urllib.request
api_token = "YOUR_API_TOKEN"
signing_key = "YOUR_BASE64_SIGNING_KEY"
body = json.dumps({
"Store_Code": "YOUR_STORE_CODE",
"Miva_Request_Timestamp": int(time.time()),
"Function": "ProductList_Load_Query",
"Count": 1,
"Offset": 0,
}).encode("utf-8")
# Signing keys from the admin can be missing their trailing "=" padding,
# which Python's decoder requires.
key = base64.b64decode(signing_key + "=" * (-len(signing_key) % 4))
# Sign the exact bytes being sent.
digest = hmac.new(key, body, hashlib.sha256).digest()
signature = base64.b64encode(digest).decode("ascii")
request = urllib.request.Request(
"https://www.example.com/mm5/json.mvc",
data=body,
method="POST",
headers={
"Content-Type": "application/json",
"X-Miva-API-Authorization": f"MIVA-HMAC-SHA256 {api_token}:{signature}",
},
)
with urllib.request.urlopen(request) as response:
print(response.read().decode("utf-8"))
The response is the same as an unsigned request. See Read the response.
The API clients for PHP, Node.js, Python, Swift and C# sign requests for you. Use one if your language has one.
Common signing mistakes#
- Signing a different string than you send. Serialize the body once and use that same string for both the signature and the request. Re-encoding the object (for example, letting an HTTP library serialize it again) can change spacing, key order or escaping, and the signature no longer matches.
- Forgetting to Base64-decode the signing key. The key from the admin is Base64 text; HMAC needs the decoded bytes.
- Hex-encoding the digest. Miva expects the raw binary digest, Base64-encoded. In PHP, pass
trueas the last argument tohash_hmac. - Sending a stale timestamp. With the timestamp requirement on, the request has to arrive within 30 seconds of
Miva_Request_Timestamp. Set it when you build the request, and keep the server clock in sync. - Using the wrong header format. It’s
MIVA-HMAC-SHA256, a space, the access token, a colon, then the signature, with no spaces around the colon. - Expecting a signature error when the problem is permissions. A correct signature still fails if the token doesn’t have the function.
Troubleshooting#
| What you see | Likely cause |
|---|---|
| No response at all | The X-Miva-API-Authorization header is missing or its name is misspelled |
Invalid request signature |
The signature doesn’t match: wrong key, key not decoded, digest hex-encoded, or the body was changed after signing. This also appears when the token expects a signature and the header has none. |
Missing required timestamp |
The token requires a timestamp and the body has no Miva_Request_Timestamp |
Timestamp outside configured window |
The timestamp is too old or too far ahead. Check the server clock. |
Token does not require signature |
The token’s signature requirement is off. Turn it on in the token’s settings. |
Function not assigned to token |
Authentication worked. The token doesn’t have permission for this function. |
Access denied |
Invalid token, wrong header type, wrong content type, IP address not on the token’s list, or missing permissions |
The troubleshooting page has the full list of authentication errors.
Next steps#
- Set up Postman to try other functions without writing code
- Requests and responses
- Function reference