Status: Stable.
Signing and Idempotency
Canonical signing string
More Rewards verifies the HMAC signature using this canonical string:
{timestamp}
{method}
{path}
{body_sha256}
Where:
timestampis the exact rawX-MR-Timestampheader valuemethodis the uppercase HTTP methodpathis the request pathname onlybody_sha256is the lowercase hex SHA-256 of the raw request body
For GET requests, body_sha256 is the hash of an empty string.
Signature header
X-MR-Signature: v1=<hex_hmac_sha256>
JavaScript example
import crypto from "crypto";
function sha256Hex(input) {
return crypto.createHash("sha256").update(input, "utf8").digest("hex");
}
function hmacHex(secret, input) {
return crypto.createHmac("sha256", secret).update(input, "utf8").digest("hex");
}
async function signedRequest({ baseUrl, keyId, secret, method, path, body, idempotencyKey }) {
const timestamp = new Date().toISOString();
const bodyText = method === "GET" ? "" : JSON.stringify(body ?? {});
const canonical = `${timestamp}\n${method}\n${path}\n${sha256Hex(bodyText)}`;
const signature = `v1=${hmacHex(secret, canonical)}`;
return fetch(`${baseUrl.replace(/\/$/, "")}${path}`, {
method,
headers: {
"Content-Type": "application/json",
"X-MR-Key-Id": keyId,
"X-MR-Timestamp": timestamp,
"X-MR-Signature": signature,
...(method === "POST" ? { "X-MR-Idempotency-Key": idempotencyKey } : {}),
},
body: method === "GET" ? undefined : bodyText,
});
}
Build the final body first, sign that exact body, and send that exact string. Do not add fields, reorder fields, or reserialize JSON after signing.
Idempotency
All POST requests require:
X-MR-Idempotency-Key: unique-per-logical-request
Same idempotency key and same payload returns the original response. Same idempotency key and different payload returns duplicate_idempotency_conflict.
Use a different idempotency key for each logical submission and each endpoint type.
