API Authentication and Credentials
Learn how mCards APIs authenticate requests using HMAC-SHA256 signing, including header format, signature construction, and code examples.
API Authentication and Credentials
All mCards 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 JSON 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 raw JSON string of the request body
- The output is a lowercase hexadecimal string
For requests with a body (POST, PUT, PATCH)
Sign the full JSON request body exactly as it will be sent.
For requests without a body (GET, DELETE)
Sign the JSON-serialized string "null" (the four-character string: n-u-l-l). This matches the behavior of JSON.stringify(null) in JavaScript or json.dumps(None) in Python.
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 mCards API."""
payload_string = json.dumps(body) if body else json.dumps(None)
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/distributors/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)
payload_string = body ? body.to_json : nil.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/distributors/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) {
const payloadString = 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/distributors/v1/distributors")
.then((data) => console.log(data));cURL
# Set your credentials
API_KEY="your_api_key"
API_SECRET="your_api_secret"
# For GET requests (signing null payload)
PAYLOAD="null"
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $NF}')
curl -X GET "https://uat-us.mcards.com/api/distributors/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=$(echo -n "$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");
const body = pm.request.body ? pm.request.body.raw : "null";
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/distributors/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
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 | JWKS URL |
|---|---|
| Feature SSO Token | {base_url}/.well-known/jwks.json |
| Payments User Token | {base_url}/api/payments/.well-known/jwks.json |
Replace {base_url} with your environment's base URL (provided during onboarding).
Validation Steps
- Fetch the JWKS — Retrieve the public key set from the appropriate JWKS endpoint. Cache the response (keys rotate infrequently — a 2-hour cache TTL is reasonable).
- 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. - 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
JWKS_URL = "{base_url}/api/payments/.well-known/jwks.json"
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"
)
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 Webhooks API Concepts.
Updated 2 days ago