Summary
Mapp Connect (MCT) is one of our data interfaces to Mapp Engage. The full introduction can be found here, and the API endpoints here.
Authentication
The Integration API uses JSON Web Tokens (JWT) to authenticate requests. This article explains how JWT authentication works in this context and provides a working PHP example.
What is a JWT?
A JWT is three dot-separated segments: header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJyZXF1ZXN0LWhhc2giOiJhYmMxMjMiLCJleHAiOjE3NzQzNTc4NTczNzJ9.s9G3fK7pL2...
|________________________|.___________________________________________________|.________________|
header payload signatureThe header and payload are not encrypted — they're simply base64-encoded and anyone can decode and read them. The signature is what proves the token hasn't been tampered with. Only someone with the shared secret key can produce a valid signature for a given header and payload.
How the Authentication Flow Works
Step 1: Build a fingerprint of your request
Concatenate the request components into a single pipe-delimited string:
{urlPath}|{postBody}|{queryString}urlPath — The API path, starting from
/api/v(do not include any proxy prefix such as/charon)postBody — The raw POST body string, if present
queryString — The query string (without the leading
?), if present
Only include each pipe and component if the value is non-empty. For example, a GET request with no body and no query string would just be the URL path alone.
Step 2: SHA1-hash the fingerprint
Hash the pipe-delimited string with SHA1 to produce a lowercase hex digest. This becomes a compact fingerprint of what you're requesting.
Step 3: Build the JWT
Construct the three JWT segments:
Header — A JSON object specifying the signing algorithm:
json
{"alg": "HS256"}Payload — A JSON object containing:
request-hash— The SHA1 hex digest from Step 2exp— Expiry timestamp in milliseconds (current time in ms + 600000 for a 10-minute window)
Signature — Computed by:
Base64url-encode the header and payload separately
Join them with a dot:
encodedHeader.encodedPayloadSign that string using HMAC-SHA256 with the shared secret key
Base64url-encode the resulting signature
The final token is: encodedHeader.encodedPayload.encodedSignature
Step 4: Send the request
Include the JWT in the auth-token HTTP header.
What the server does
The server reverses the process:
Decodes the JWT header and payload
Verifies the signature using its copy of the shared secret
Checks the
expclaim to ensure the token hasn't expiredRebuilds the pipe-delimited string from the request it received
SHA1-hashes it and compares against the
request-hashclaim
If all three checks pass (valid signature, not expired, hash matches), the request is trusted.
Terminology
Term | Meaning |
|---|---|
HS256 | HMAC-SHA256 — the "H" is HMAC (a keyed hashing scheme), the "S256" is SHA-256 (the hash algorithm) |
Base64url encoding | Standard base64 with |
HMAC | Hash-based Message Authentication Code — a way to produce a hash that requires a secret key, so only parties who know the key can produce or verify it |
Common Pitfalls
Proxy prefixes in the URL path — If your request routes through a proxy (e.g.
/charon/api/v1/...), strip the proxy prefix before hashing. The hash should use the path starting from/api/v1/.... Use the full path (with prefix) only in the actual HTTP request URL.Variable naming — Ensure the variable you check in your
if (!empty(...))conditions is the same one you append. A typo here means a component silently gets excluded from the hash.JSON formatting — If the server normalises JSON before hashing, whitespace differences in the POST body will cause a mismatch. Consider using compact JSON (
json_encode(json_decode($body))) if you encounter this.Expiry units — The
expclaim uses milliseconds, not seconds. PHP'stime()returns seconds, so multiply by 1000 before adding the offset.
Working PHP Example
php
<?php
// --- Configuration ---
$baseUrl = 'https://jamie.g.shortest-route.com';
$proxyPrefix = '/charon';
$urlPath = '/api/v1/integration/{your-integration-id}/event/batch';
$queryString = 'subtype=user';
$secretKey = 'your-secret-key';
$postBody = json_encode([
["email" => "user1@example.com", "group" => "2400812413"],
["email" => "user2@example.com", "group" => "2400812413"]
]);
// --- Helper Functions ---
// Base64url encode: URL-safe base64 with no padding
function base64UrlEncode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
// HMAC-SHA256, returned as a base64url-encoded string
function hmacSha256($data, $secret) {
return base64UrlEncode(hash_hmac('sha256', $data, $secret, true));
}
// SHA1 hash, returned as a lowercase hex string
function sha1Hex($data) {
return strtolower(sha1($data));
}
// Build a signed JWT containing the request hash and expiry
function buildJwt($requestHash, $secret) {
$header = ['alg' => 'HS256'];
$payload = [
'request-hash' => $requestHash,
'exp' => time() * 1000 + 600000 // 10 minutes from now, in milliseconds
];
$encodedHeader = base64UrlEncode(json_encode($header));
$encodedPayload = base64UrlEncode(json_encode($payload));
$unsignedToken = $encodedHeader . '.' . $encodedPayload;
$signature = hmacSha256($unsignedToken, $secret);
return $unsignedToken . '.' . $signature;
}
// --- Build the request hash ---
// Use the API path (without proxy prefix) for hashing
$tokenData = $urlPath;
if (!empty($postBody)) $tokenData .= "|" . $postBody;
if (!empty($queryString)) $tokenData .= "|" . $queryString;
$requestHash = sha1Hex($tokenData);
$jwtToken = buildJwt($requestHash, $secretKey);
// --- Send the request ---
// Use the full path (with proxy prefix) for the actual HTTP call
$ch = curl_init($baseUrl . $proxyPrefix . $urlPath . '?' . $queryString);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"auth-token: $jwtToken"
]);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?>Debugging Tips
If you receive a 401 Unauthorized with the message "Failed to verify request hash", the SHA1 hash your code produces doesn't match what the server computed. Add debug output to inspect the exact string being hashed:
php
echo "tokenData: " . $tokenData . "\n";
echo "requestHash: " . $requestHash . "\n";
// Decode the JWT to verify its contents
$parts = explode('.', $jwtToken);
echo "JWT header: " . base64_decode(strtr($parts[0], '-_', '+/')) . "\n";
echo "JWT payload: " . base64_decode(strtr($parts[1], '-_', '+/')) . "\n";Compare this output against a known working implementation to identify where the inputs diverge.