API Authentication and Credentials
Learn how mCard APIs authenticate requests using HMAC-SHA256 signing, including header format, signature construction, and code examples.
API Authentication and Credentials
All mCard APIs authenticate requests using HMAC-SHA256 request signing. Every API call must include a valid Authorization header containing your API key and a signature computed from the request body.
Credentials
When you are onboarded as a development partner, mCards provides two credentials:
| Credential | Purpose |
|---|---|
API Key (hmac_key) | Identifies your partner account. Included in the Authorization header. |
API Secret (hmac_secret) | Used to compute the HMAC-SHA256 signature. Never transmitted in requests. |
You receive separate credentials for sandbox (UAT) and production environments. Keep your API secret confidential — it should only exist on your server, never in client-side code or version control.
Authorization Header Format
Every request must include an Authorization header in the following format:
Authorization: HMAC_256 {api_key};{signature}
Where:
{api_key}is your API key (thehmac_keyprovided during onboarding){signature}is the HMAC-SHA256 hex digest of the request body, computed using your API secret
The key and signature are separated by a semicolon (;) with no spaces.
Computing the Signature
The signature is computed by taking the HMAC-SHA256 hex digest of the raw request body using your API secret as the key.
Algorithm
signature = HMAC-SHA256(secret, request_body)
Where:
- secret = Your API secret (
hmac_secret) - request_body = The exact raw body string sent over the wire, UTF-8 encoded (the empty string when there is no body)
- The output is a lowercase hexadecimal string
Query-string parameters, the HTTP method, the path, and headers are not part of the signature — only the body is signed.
For requests with a body (POST, PUT, PATCH)
Sign the exact raw JSON string you send. Serialize the body once, sign that string, and transmit that same string — re-serializing after signing (different key order, added whitespace, pretty-printing) produces a different signature and fails verification.
For requests without a body (GET, DELETE)
Sign the empty string ("").
Do not sign the literal null — signing the four-character string null is the most common cause of 401 invalid_hmac on GET and DELETE requests.
Worked example
Use these fixed values to verify your signing code offline, with the secret test_secret:
| Request | Signed string | Expected signature |
|---|---|---|
GET/DELETE (no body) | `` (empty) | f7f9bd47fb987337b5796fdc1fdb9ba221d0d5396814bfcaf9521f43fd8927fd |
POST with body {"foo":"bar"} | {"foo":"bar"} | 7b38395971c202e8b23b8ae5aa82bb18b3d2bf1df564a37257d5185f83f75a1e |
Code Examples
Python
import hmac
import hashlib
import json
import requests
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
BASE_URL = "https://uat-us.mcards.com" # or https://app-us.mcards.com for production
def make_request(method, path, body=None):
"""Make an authenticated request to the mCard API."""
# Requests without a body are signed over the empty string
payload_string = json.dumps(body, separators=(",", ":")) if body else ""
signature = hmac.new(
API_SECRET.encode("utf-8"),
payload_string.encode("utf-8"),
hashlib.sha256
).hexdigest()
headers = {
"Authorization": f"HMAC_256 {API_KEY};{signature}",
"Content-Type": "application/json"
}
response = requests.request(
method,
f"{BASE_URL}{path}",
headers=headers,
data=payload_string if body else None
)
return response.json()
# Example: List distributors
distributors = make_request("GET", "/api/distributor/v1/distributors")
# Example: Create a webhook subscription
webhook = make_request("POST", "/api/webhook/webhooks", {
"url": "https://your-server.com/webhooks",
"event_type": "transaction"
})Ruby
require "openssl"
require "json"
require "net/http"
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
BASE_URL = "https://uat-us.mcards.com" # or https://app-us.mcards.com for production
def make_request(method, path, body = nil)
# Requests without a body are signed over the empty string
payload_string = body ? body.to_json : ""
signature = OpenSSL::HMAC.hexdigest(
"sha256",
API_SECRET,
payload_string
)
uri = URI("#{BASE_URL}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = case method
when :get then Net::HTTP::Get.new(uri)
when :post then Net::HTTP::Post.new(uri)
when :put then Net::HTTP::Put.new(uri)
when :delete then Net::HTTP::Delete.new(uri)
end
request["Authorization"] = "HMAC_256 #{API_KEY};#{signature}"
request["Content-Type"] = "application/json"
request.body = payload_string if body
response = http.request(request)
JSON.parse(response.body)
end
# Example: List distributors
distributors = make_request(:get, "/api/distributor/v1/distributors")JavaScript (Node.js)
const crypto = require("crypto");
const https = require("https");
const API_KEY = "your_api_key";
const API_SECRET = "your_api_secret";
const BASE_URL = "https://uat-us.mcards.com"; // or https://app-us.mcards.com for production
function makeRequest(method, path, body = null) {
// Requests without a body are signed over the empty string
const payloadString = body ? JSON.stringify(body) : "";
const signature = crypto
.createHmac("sha256", API_SECRET)
.update(payloadString)
.digest("hex");
const url = new URL(path, BASE_URL);
return fetch(url, {
method: method,
headers: {
Authorization: `HMAC_256 ${API_KEY};${signature}`,
"Content-Type": "application/json",
},
body: method !== "GET" ? payloadString : undefined,
}).then((res) => res.json());
}
// Example: List distributors
makeRequest("GET", "/api/distributor/v1/distributors")
.then((data) => console.log(data));cURL
# Set your credentials
API_KEY="your_api_key"
API_SECRET="your_api_secret"
# For GET and DELETE requests, sign the empty string
PAYLOAD=""
SIGNATURE=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $NF}')
curl -X GET "https://uat-us.mcards.com/api/distributor/v1/distributors" \
-H "Authorization: HMAC_256 ${API_KEY};${SIGNATURE}" \
-H "Content-Type: application/json"
# For POST requests (signing the JSON body)
PAYLOAD='{"url":"https://your-server.com/webhooks","event_type":"transaction"}'
SIGNATURE=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $NF}')
curl -X POST "https://uat-us.mcards.com/api/webhook/webhooks" \
-H "Authorization: HMAC_256 ${API_KEY};${SIGNATURE}" \
-H "Content-Type: application/json" \
-d "$PAYLOAD"Postman Pre-request Script
If you use Postman for testing, add this as a Pre-request Script to automatically sign requests:
// Set these as Postman environment variables:
// - mcards_api_key
// - mcards_api_secret
const apiKey = pm.environment.get("mcards_api_key");
const apiSecret = pm.environment.get("mcards_api_secret");
// Bodyless requests (GET, DELETE) are signed over the empty string
const body = (pm.request.body && pm.request.body.raw) || "";
const signature = CryptoJS.HmacSHA256(body, apiSecret).toString(CryptoJS.enc.Hex);
pm.request.headers.add({
key: "Authorization",
value: `HMAC_256 ${apiKey};${signature}`
});Environments
mCards provides two environments:
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox (UAT) | https://uat-us.mcards.com | Development and testing |
| Production | https://app-us.mcards.com | Live transactions |
API routes are appended to the base URL. For example, to list distributors in sandbox:
GET https://uat-us.mcards.com/api/distributor/v1/distributors
- Use sandbox for all development and integration testing
- Each environment has its own set of API credentials
- API behavior is identical between environments; only the data and base URL differ
Troubleshooting
Common Authentication Errors
| Error | Cause | Fix |
|---|---|---|
401 Unauthorized — "Invalid Developer Partner" | API key not recognized | Verify you are using the correct hmac_key for the target environment |
401 Unauthorized — "Inactive Developer Partner" | Partner account is disabled | Contact your mCards partner manager |
403 Forbidden | Valid credentials but insufficient permissions | Verify your role grants access to the requested API library (see API Scoping and Permissions) |
Signature Debugging Checklist
If you receive authentication errors, verify:
- Correct secret — You are using the
hmac_secret(not thehmac_key) to compute the signature - Correct payload — The signed string exactly matches the raw request body sent over the wire
- Correct environment — Sandbox credentials do not work in production and vice versa
- JSON serialization — The body is serialized consistently (key order, whitespace) between signing and sending
- Encoding — Both the secret and payload are treated as UTF-8 strings
- Bodyless requests — GET and DELETE requests are signed over the empty string, not over
null. Query parameters are never signed.
Token Validation (JWKS)
When your application receives a Feature SSO Token or Payments User Token from mCards (during embedded partner onboarding flows), you must validate the token's signature before trusting its claims.
JWKS Endpoint
mCards publishes its public keys in JSON Web Key Set (JWKS) format:
| Token Type | Environment | JWKS URL |
|---|---|---|
| Feature SSO Token | Production | https://app-us.mcards.com/api/features_marketplace/.well-known/jwks.json |
| Feature SSO Token | Sandbox (UAT) | https://uat-us.mcards.com/api/features_marketplace/.well-known/jwks.json |
| Payments User Token | Production | https://app-us.mcards.com/api/payments/.well-known/jwks.json |
| Payments User Token | Sandbox (UAT) | https://uat-us.mcards.com/api/payments/.well-known/jwks.json |
Production and sandbox publish different key sets — a sandbox token never validates against production keys.
Discovery Endpoint (optional)
If your JWT library prefers OpenID Connect discovery over a hardcoded JWKS URL, Feature SSO also exposes:
GET https://app-us.mcards.com/api/features_marketplace/.well-known/openid-configuration
GET https://uat-us.mcards.com/api/features_marketplace/.well-known/openid-configuration
The response contains a jwks_uri field — use it as-is. It may name the marketplace host alias
(fm.mcards.com in production, fm-uat.mcards.com in sandbox) rather than the host you requested;
both serve the same key set, so either URL is valid.
Validation Steps
- Fetch the JWKS — Retrieve the public key set from the appropriate JWKS endpoint. Cache the response (a 1–2 hour TTL is reasonable) and refetch immediately when a token presents a
kidyou do not have cached — that handles key rotation without any change on your side. - Decode the JWT header — Extract the
kid(key ID) from the token's header to identify which key to use. - Verify the signature — Use the matching public key to verify the token's RSA signature (RS256 algorithm).
- Check standard claims — Verify
exp(expiration),iss(issuer), andaud(audience) claims match expected values. Both token types carry the Features Marketplace base URL for the issuing environment asiss(https://fm.mcards.com/api/features_marketplace/in production,https://fm-uat.mcards.com/api/features_marketplace/in sandbox — trailing slash included), which is not the same host as the JWKS URL above. - Extract identity claims — Use the validated claims (
consumer_id,cardholder_card, etc.) to identify the cardholder.
Example: Token Validation in Python
import jwt
import requests
# Sandbox (UAT); use https://app-us.mcards.com/... and https://fm.mcards.com/... in production
JWKS_URL = "https://uat-us.mcards.com/api/payments/.well-known/jwks.json"
ISSUER = "https://fm-uat.mcards.com/api/features_marketplace/"
def validate_mcards_token(token):
"""Validate an mCards JWT and return its claims."""
# Fetch JWKS (cache this in production)
jwks = requests.get(JWKS_URL).json()
# Decode and verify the token
header = jwt.get_unverified_header(token)
# Find the matching key by kid
key = next(k for k in jwks["keys"] if k["kid"] == header["kid"])
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key)
claims = jwt.decode(
token,
public_key,
algorithms=["RS256"],
audience="expected_audience",
issuer=ISSUER
)
return claimsNote: Feature SSO Tokens expire after 5 minutes and are intended for single-use during onboarding. Do not cache or reuse these tokens. See Feature SSO Token and Payments User Token for token-specific claim details.
Webhook Security vs. API Authentication
API authentication (this page) and webhook security are separate mechanisms:
| API Authentication | Webhook Security | |
|---|---|---|
| Direction | Your server → mCards | mCards → Your server |
| Mechanism | HMAC-SHA256 of request body | Signature verification of webhook payload |
| Credentials | Your API key + secret | Webhook-specific signing secret |
| Purpose | Prove your identity to mCards | Verify webhook came from mCards |
For webhook security details, see Webhook API Concepts.
Updated about 2 months ago