Security & Conventions
These rules apply to every endpoint, card and QR.
Transport
- MineSec calls your adapter over HTTPS only. Plain HTTP is not accepted outside local development.
- All requests are
POSTwith a JSON body,Content-Type: application/json, UTF-8. - Endpoints sit under the base URL you register, for example
https://api.your-host.com/api/v1/transactions.
Request headers
POST /api/v1/transactions HTTP/1.1
Host: api.your-host.com
Accept: application/json
Content-Type: application/json
Authorization: Bearer <your-credential>
x-minesec-request-id: 01JKF44HP0VYHTB0SNC0Y6VJQE
x-request-time: 2025-02-07T02:34:33Z
x-signature: HmacSHA256 9f07909a151f50bb3280f955e4be659a4d081275428fa2bae6b9e07510056edb| Header | Meaning |
|---|---|
Authorization | The credential you issued to MineSec during onboarding (opaque token or JWT). |
x-minesec-request-id | ID (ULID) of the POS request behind this message. A card reversal and a batch upload carry the same ID as the original transaction. |
x-request-time | Signing time, ISO 8601 UTC to the second. |
x-signature | HMAC-SHA256 signature of the request. See Message signing. |
Authorization, x-request-time and x-signature are sent on signed requests only:
- QR requests are always signed.
- Card requests are signed when MineSec has enabled signing for your account. Ask your MineSec integration contact
to enable it during onboarding. An unsigned card request carries only
x-minesec-request-id, so protect your endpoint by other means, such as allow-listing MineSec's source IPs.
Message signing
Signed requests use a shared HMAC key (hex encoded) agreed during onboarding. The signed string joins four
parts with two dots (..):
{HTTP_METHOD}..{PATH}..{x-request-time}..{BODY}Example:
POST../api/v1/transactions..2025-02-07T02:34:33Z..{"enbrTranId":"tran_01JKF44JBWRNH1AG0GMMKA1M1B","trace":"044883",...}The header value is HmacSHA256 followed by the lower-case hex digest.
To verify on your side:
- Hex-decode the HMAC key to bytes.
- Rebuild the string from the method, the full request path as received (base path included, query excluded), the
x-request-timeheader and the raw body bytes as received. Do not re-serialize the JSON. - Compute HMAC-SHA256 and compare it with the header in constant time.
- Reject requests whose
x-request-timeis too far from your clock (we suggest 5 minutes).
import crypto from "node:crypto"
function verify(req, rawBody, hmacKeyHex) {
const time = req.headers["x-request-time"]
// full path: req.path is relative to the mount point in Express
const path = req.originalUrl.split("?")[0]
const data = `${req.method}..${path}..${time}..${rawBody}`
const expected = crypto.createHmac("sha256", Buffer.from(hmacKeyHex, "hex")).update(data).digest("hex")
const received = (req.headers["x-signature"] || "").replace(/^HmacSHA256 /, "")
return received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
}QR notifies that you send to MineSec are signed the same way with a separate key. See QR notify.
Idempotency
MineSec may resend a request after a network error. Your adapter must not process the same operation twice.
-
Card: do not de-duplicate on
x-minesec-request-idalone. A reversal reuses the ID of the transaction it reverses, so it would be mistaken for a repeat. Use these keys instead:Endpoint Idempotency key /transactionsenbrTranId+actionType+isReversal/batch-uploadsenbrTranId+batchId/settlementsbatchId+isSecond -
QR:
enbrTranIdis the idempotency key. A repeated create must return the result of the first one and must not create a second order. A repeated cancellation carries the sameenbrActionId.
Responses
Always answer with HTTP 200 and a JSON body, including declines. MineSec reads the outcome from the body only; the
HTTP status code is not used to decide it:
- Card:
respCode: "00"means approved or successful. Any other value is a decline or rejection. - QR:
statusdecides the outcome.respCodeis kept for diagnostics and must be00whenstatusisPENDINGorSUCCESS.
If no response arrives in time, MineSec starts its recovery flow: a reversal for card, inquiries and a cancellation for QR. A body that cannot be parsed is treated as a decline, with no recovery. See Timeout and reversal and QR Payments.
Formatting
| Item | Format |
|---|---|
| Currency | ISO 4217 alpha-3, e.g. USD, HKD |
| Amount | Decimal string with the currency's ISO decimal places: "1.00" for USD, "1000" for JPY |
| Timestamp | ISO 8601 with offset, e.g. 2025-02-11T02:37:57.311Z. Parse the offset; do not assume UTC |
| Country | ISO 3166-1 alpha-2 |
| Extra data | posExtra / extra: flat key-value map of strings. posExtra carries data from the POS and your own keys from the acceptance's extraParams |
| Null | Null fields are omitted from requests |
Sensitive card data
- MineSec never sends clear PIN. PIN blocks are translated inside an HSM under your PIN key before they are sent.
- With a Track2 key (recommended), Track2 is sent encrypted under your DUKPT key in
track2Data. Without one, Track2 is not sent. EMV tags99and57are never sent incardData. - PIN and Track2 keys are exchanged through a key ceremony with MineSec, never by email or in this portal.
See PIN and Track2 for details.
Treat the HMAC key, notify key and your bearer credential as secrets. Store them in a secrets manager, rotate them with MineSec if they are exposed, and never log them.