More Rewards
More Rewards
Developer Documentation

Status: Stable.

Signing and Idempotency

Canonical signing string

More Rewards verifies the HMAC signature using this canonical string:

{timestamp}
{method}
{path}
{body_sha256}

Where:

  • timestamp is the exact raw X-MR-Timestamp header value
  • method is the uppercase HTTP method
  • path is the request pathname only
  • body_sha256 is 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.