Skip to main content

Authentication

This API uses an HMAC-SHA256 signature derived from the request path and the SHA256 digest of the request body. The final signature is folded five times and sent in the Authorization header together with your API account key.


Required Headers

X-Api-Key: <your API account key>
Authorization: HMAC <base64_signature>
Accept: application/json

About API Keys

Each SMS account can hold up to 5 API keys at any one time. Newly issued keys carry an mps_ prefix and a matching secret; an optional expiry date can be set at creation. Keys that pre-date the rotation feature remain valid in their original 8-character form — they continue to work and do not need to be replaced until you choose to.

  • Multiple active keys let you rotate without downtime: create the replacement, switch your integration over, then revoke the old key.
  • Expiry is required when creating via the API. Choose 30d, 90d, 180d, or 365d. Non-expiring keys can only be created from the SMS web UI.
  • The secret is returned only once — at the moment of creation. Store it securely; it cannot be retrieved later.

Keys can be managed through the SMS web UI or programmatically through the API. See API Keys for the create and revoke endpoints.


Key Expiry Headers

When you authenticate with a key that has an expiry set, every response includes:

HeaderWhen presentDescription
X-Api-Key-ExpiresAlways, if the key has an expiryISO 8601 timestamp of the expiry.
X-Api-Key-Expires-InOnly when 30 days or fewer remainRelative time left: Nd for days, Nh for hours in the final day.

Monitor these headers to rotate keys before they expire. Requests made with an expired key — or against an account whose API access has been disabled — are rejected with 401 Unauthorized.


What Gets Signed

The following steps show exactly how the signature is calculated. You can verify your implementation by reproducing the example below, which signs a request to create a supplier.

For this worked example:

  • API Secret: 6OZ0oHt0y3ICVaje95yGrQtt
  • Folds: 5

Folds are fixed at 5 for every account; there is no per-account configuration. Follow along to ensure your implementation produces the same output.


Step 1 - Canonical Path

Use the exact request path, including the leading slash and excluding any query parameters.

Example:

/api/v1/suppliers

Step 2 - Body Digest

Compute the SHA256 hash of the raw request body bytes, then encode the result as a lowercase hex string.

If the request has no body (e.g. GET), compute the SHA256 of the empty string "".

Example request body:

{"supplier":{"trading_name":"New Supplier Ltd","vendor_code":"NS002"}}

SHA256 digest:

e9bafdf140aed2d704843d1ec702fc360fca435470f4dc1cdb1a201dc20ae5d8

Step 3 - Build the String to Sign

Concatenate the canonical path and the body digest with no delimiter.

Example:

/api/v1/supplierse9bafdf140aed2d704843d1ec702fc360fca435470f4dc1cdb1a201dc20ae5d8

Step 4 - Fold the Digest (Repeated HMAC)

Perform the following operation five times:

stringToSign[i+1] = HMAC_SHA256_HEX(secret, stringToSign[i])

Each fold uses the hex output of the previous fold as its input.


Step 5 - Base64 Encode the Final Fold

Take the hex output of the final fold and Base64-encode it:

YmQ5MzUyNTIxNGMyNmQ2ODdmNmFhNzQ1MTY1NjBmNWVhZTUwMmQ1OWEyODMwYWJiNDc5ZjY1NTYxOGMyMTA3OQ==

This is the final signature.


Step 6 - Build the Authorization Header

Insert the signature into the header exactly as follows:

Authorization: HMAC YmQ5MzUyNTIxNGMyNmQ2ODdmNmFhNzQ1MTY1NjBmNWVhZTUwMmQ1OWEyODMwYWJiNDc5ZjY1NTYxOGMyMTA3OQ==

Your request is now fully authenticated and ready to send.


Example Code (NodeJS)

const crypto = require("crypto");
const axios = require("axios");

const FOLDS = 5;

async function generateSignature(path, payload, secretKey) {
const payloadString = payload ? JSON.stringify(payload) : "";
const payloadHash = crypto
.createHash("sha256")
.update(payloadString)
.digest("hex");

const stringToSign = `${path}${payloadHash}`;

let signature = crypto
.createHmac("sha256", secretKey)
.update(stringToSign)
.digest("hex");

for (let i = 1; i < FOLDS; i++) {
signature = crypto
.createHmac("sha256", secretKey)
.update(signature)
.digest("hex");
}

return Buffer.from(signature).toString("base64");
}

async function createSupplier() {
const API_SECRET_KEY = process.env.API_SECRET_KEY;
const API_ACCOUNT_KEY = process.env.API_ACCOUNT_KEY;

const url = "https://www.suppliermanagement.co.za/api/v1/suppliers";
const payload = {
supplier: {
trading_name: "New Supplier Ltd",
vendor_code: "NS002",
},
};
const path = new URL(url).pathname;
const base64Signature = await generateSignature(path, payload, API_SECRET_KEY);

try {
const response = await axios.post(url, payload, {
headers: {
Accept: "application/json",
Authorization: `HMAC ${base64Signature}`,
"X-Api-Key": API_ACCOUNT_KEY,
},
});

console.log("Response:", response.data);
} catch (error) {
console.error("Error:", error.message);
}
}

createSupplier();