Authentication

Every request best-system sends to your wallet API carries 2 headers — always verify them before processing; if verification fails, never touch the wallet

Headers on every request

HeaderValue
X-Api-KeyThe API key best-system issued to you — check it matches the one you hold
X-SignatureHMAC-SHA256(rawBody, secret) encoded as hex

The body is always JSON (Content-Type: application/json)

Verify from the raw bytes only

The signature is computed over the entire raw body, byte for byte, with the secret best-system issued — so your side must keep the raw request bytes, verify against them first, and only then parse the JSON

Never re-serialize

Do not JSON.parse and then JSON.stringify the body back to compute the signature — a different key order, whitespace, or number formatting changes even a single byte and the signature will mismatch on a perfectly valid request

Example middleware for every endpoint under /api (Express):

typescript
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const app = express();

// Capture raw bytes first — the signature is over the raw body; never parse before verifying
app.use("/api", express.raw({ type: "application/json" }));

app.use("/api", (req, res, next) => {
  if (req.headers["x-api-key"] !== process.env.BS_API_KEY) {
    return res.json({ code: 40003 });
  }

  const expected = createHmac("sha256", process.env.BS_SECRET!)
    .update(req.body) // raw bytes — never JSON.parse then stringify back
    .digest("hex");
  const given = String(req.headers["x-signature"] ?? "");
  const ok =
    given.length === expected.length &&
    timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!ok) return res.status(401).json({ code: 30002 });

  req.body = JSON.parse(req.body.toString("utf8"));
  next();
});

Use timingSafeEqual to prevent timing attacks — and compare lengths first, because the function throws when the buffers differ in length

When the signature does not match

Two accepted replies (best-system handles both):

  • HTTP 200 with a body carrying code: 30002
  • HTTP 401

Either way, do not process any txns[] — see the full catalog at Seamless codes

Where the credentials come from

  • best-system issues your api_key + secret via the bo screen: Api Management → API Credential
  • Your seamless_callback_url (the base URL of all endpoints) is registered together with that credential
  • Keep the secret server-side only — never embed it in a client/mobile app; rotate it by coordinating with the best-system team