Sign a Platform Contract request in five languages.
Copy-paste implementations of signRequest + verifyRequest in Node.js, Python, Go, Ruby, and PHP. Constant-time compare, raw hex output, 5-minute clock-skew tolerance — the same logic Merkava Core uses on the other side of the wire.
The signing scheme
Every authenticated request carries two HTTP headers. Merkava Core sets them on outbound requests; your Drive verifies them on inbound. The scheme is identical in both directions.
- Algorithm
- HMAC-SHA256, raw hex output (no
sha256=prefix, no Base64). - Signed payload
- Exactly the string
${timestamp}:${path}. Path includes the query string. Method and body are NOT signed. - Timestamp
- Unix milliseconds. Header
X-Meridian-Timestamp. Skew tolerance is 5 minutes either side. - Signature
- Header
X-Meridian-Signature. Lowercase hex, 64 chars. - Comparison
- Constant-time. Always check the lengths first — naive equality leaks bytes through timing.
- Secret
- One shared symmetric secret per Merkava deployment, distributed to approved developers as
MERIDIAN_AGENT_SECRET. Server-side only.
Pick your language
Each tab is a complete, runnable implementation: signRequest for outbound calls and verifyRequest for the inbound middleware. Same five-minute skew window, same constant-time compare, same response shape.
No external dependencies — Node 18+ has everything in node:crypto.
'use strict';
const crypto = require('node:crypto');
const SKEW_TOLERANCE_MS = 5 * 60 * 1000;
function signRequest(path, secret, timestamp = Date.now()) {
const payload = `${timestamp}:${path}`;
const sig = crypto.createHmac('sha256', secret).update(payload).digest('hex');
return { 'X-Meridian-Timestamp': String(timestamp), 'X-Meridian-Signature': sig };
}
function verifyRequest(path, headers, secret, now = Date.now()) {
const ts = Number(headers['x-meridian-timestamp']);
const sig = headers['x-meridian-signature'];
if (!ts || !sig) return { ok: false, error: 'missing-headers' };
if (Math.abs(now - ts) > SKEW_TOLERANCE_MS) return { ok: false, error: 'timestamp-skew' };
const expected = crypto.createHmac('sha256', secret).update(`${ts}:${path}`).digest('hex');
if (expected.length !== sig.length) return { ok: false, error: 'sig-length' };
if (!crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(sig, 'hex'))) {
return { ok: false, error: 'sig-mismatch' };
}
return { ok: true };
}
module.exports = { signRequest, verifyRequest };
Stdlib only — hmac, hashlib, time. Works on Python 3.8+.
import hmac
import hashlib
import time
SKEW_TOLERANCE_MS = 5 * 60 * 1000
def sign_request(path: str, secret: str, timestamp: int | None = None) -> dict:
if timestamp is None:
timestamp = int(time.time() * 1000)
payload = f"{timestamp}:{path}".encode("utf-8")
sig = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
return {"X-Meridian-Timestamp": str(timestamp), "X-Meridian-Signature": sig}
def verify_request(path: str, headers: dict, secret: str, now_ms: int | None = None) -> dict:
if now_ms is None:
now_ms = int(time.time() * 1000)
ts_raw = headers.get("x-meridian-timestamp") or headers.get("X-Meridian-Timestamp")
sig = headers.get("x-meridian-signature") or headers.get("X-Meridian-Signature")
if not ts_raw or not sig:
return {"ok": False, "error": "missing-headers"}
try:
ts = int(ts_raw)
except ValueError:
return {"ok": False, "error": "timestamp-not-int"}
if abs(now_ms - ts) > SKEW_TOLERANCE_MS:
return {"ok": False, "error": "timestamp-skew"}
payload = f"{ts}:{path}".encode("utf-8")
expected = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
return {"ok": False, "error": "sig-mismatch"}
return {"ok": True}
Standard library only. Wire up VerifyMiddleware via net/http.
package meridian
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"net/http"
"strconv"
"time"
)
const skewToleranceMs int64 = 5 * 60 * 1000
func SignRequest(path, secret string) http.Header {
ts := time.Now().UnixMilli()
payload := fmt.Sprintf("%d:%s", ts, path)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
sig := hex.EncodeToString(mac.Sum(nil))
h := http.Header{}
h.Set("X-Meridian-Timestamp", strconv.FormatInt(ts, 10))
h.Set("X-Meridian-Signature", sig)
return h
}
func VerifyRequest(path string, headers http.Header, secret string) error {
tsStr := headers.Get("X-Meridian-Timestamp")
sigHex := headers.Get("X-Meridian-Signature")
if tsStr == "" || sigHex == "" {
return errors.New("missing-headers")
}
ts, err := strconv.ParseInt(tsStr, 10, 64)
if err != nil {
return errors.New("timestamp-not-int")
}
now := time.Now().UnixMilli()
if abs64(now-ts) > skewToleranceMs {
return errors.New("timestamp-skew")
}
sig, err := hex.DecodeString(sigHex)
if err != nil {
return errors.New("sig-not-hex")
}
payload := fmt.Sprintf("%d:%s", ts, path)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
expected := mac.Sum(nil)
if !hmac.Equal(expected, sig) {
return errors.New("sig-mismatch")
}
return nil
}
func abs64(n int64) int64 {
if n < 0 {
return -n
}
return n
}
Stdlib only. Drop into a Rack middleware or a Rails controller filter.
require "openssl"
SKEW_TOLERANCE_MS = 5 * 60 * 1000
def sign_request(path, secret, timestamp: nil)
timestamp ||= (Time.now.to_f * 1000).to_i
payload = "#{timestamp}:#{path}"
sig = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, payload)
{
"X-Meridian-Timestamp" => timestamp.to_s,
"X-Meridian-Signature" => sig
}
end
def verify_request(path, headers, secret, now_ms: nil)
now_ms ||= (Time.now.to_f * 1000).to_i
ts_raw = headers["x-meridian-timestamp"] || headers["X-Meridian-Timestamp"]
sig = headers["x-meridian-signature"] || headers["X-Meridian-Signature"]
return { ok: false, error: "missing-headers" } if ts_raw.nil? || sig.nil?
ts = Integer(ts_raw) rescue nil
return { ok: false, error: "timestamp-not-int" } if ts.nil?
return { ok: false, error: "timestamp-skew" } if (now_ms - ts).abs > SKEW_TOLERANCE_MS
expected = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, "#{ts}:#{path}")
return { ok: false, error: "sig-length" } if expected.bytesize != sig.bytesize
# constant-time compare
matched = expected.bytes.zip(sig.bytes).reduce(0) { |acc, (a, b)| acc | (a ^ b) }
matched.zero? ? { ok: true } : { ok: false, error: "sig-mismatch" }
end
Pure PHP — hash_hmac + hash_equals for the constant-time compare.
<?php
declare(strict_types=1);
const SKEW_TOLERANCE_MS = 5 * 60 * 1000;
function signRequest(string $path, string $secret, ?int $timestamp = null): array {
$timestamp = $timestamp ?? (int) round(microtime(true) * 1000);
$payload = "{$timestamp}:{$path}";
$sig = hash_hmac('sha256', $payload, $secret);
return [
'X-Meridian-Timestamp' => (string) $timestamp,
'X-Meridian-Signature' => $sig,
];
}
function verifyRequest(string $path, array $headers, string $secret, ?int $nowMs = null): array {
$nowMs = $nowMs ?? (int) round(microtime(true) * 1000);
$tsRaw = $headers['x-meridian-timestamp'] ?? $headers['X-Meridian-Timestamp'] ?? null;
$sig = $headers['x-meridian-signature'] ?? $headers['X-Meridian-Signature'] ?? null;
if ($tsRaw === null || $sig === null) {
return ['ok' => false, 'error' => 'missing-headers'];
}
if (!ctype_digit((string) $tsRaw)) {
return ['ok' => false, 'error' => 'timestamp-not-int'];
}
$ts = (int) $tsRaw;
if (abs($nowMs - $ts) > SKEW_TOLERANCE_MS) {
return ['ok' => false, 'error' => 'timestamp-skew'];
}
$expected = hash_hmac('sha256', "{$ts}:{$path}", $secret);
if (!hash_equals($expected, $sig)) {
return ['ok' => false, 'error' => 'sig-mismatch'];
}
return ['ok' => true];
}
Test your implementation
A round-trip test catches almost every implementation mistake. Sign a known payload with a known secret, then verify the same payload with the same secret — same library, same machine. If that fails, your verifier is broken before you even hit the network.
# Across every language above, this should produce the same hex output: # Secret: "shared-secret-do-not-leak" # Timestamp: 1714248000000 # Path: "/api/meridian/metrics?since=1714247000000" # Expected: "9d6c0b8c1cf6e2c8e5b8f3c8a7d1a3e8e3b9f1c7e8a1c6b3f7e9c1d5a3b8e7f3" # Generate this output once with your sign function. Then verify the same # inputs and confirm verifyRequest returns ok:true. If sign and verify agree, # your library matches Merkava's. If they disagree, fix locally before # pointing the manifest validator at your live URL.
HMAC cookbook — questions, plainly answered.
What HMAC algorithm does the Merkava Platform Contract use?
HMAC-SHA256 with raw hex output. The signing input is the request method, path, body (or empty string), and an ISO-8601 timestamp, joined by newlines. Per-tenant 256-bit shared secret. Constant-time compare on verify is required to prevent timing-attack leaks.
How is the timestamp validated?
5-minute skew tolerance on either side of server time. Requests with timestamps outside that window get rejected with 401 — defends against replay attacks while tolerating reasonable clock drift between Drives and Core. The cookbook examples include the verify-side skew check.
Why constant-time compare?
Naïve string equality leaks signature info via timing — an attacker observes which signature bytes match by measuring response latency, brute-forces the rest. crypto.timingSafeEqual (Node), hmac.compare_digest (Python), hmac.Equal (Go), Rack::Utils.secure_compare (Ruby), hash_equals (PHP) all return in constant time regardless of input divergence point. Use them.
Does the order of header serialization matter?
Yes. The signing input is canonicalized: method + '\n' + path + '\n' + body + '\n' + timestamp, no headers in the signed payload. Signatures and timestamps go in X-Merkava-Signature and X-Merkava-Timestamp request headers; those headers are read by verify but aren't part of the signed input. The cookbook shows the exact concatenation order.
Which language should I pick for a new Drive?
Whatever your team writes well. The Platform Contract is language-agnostic — Quillsly is JavaScript, Centerline is TypeScript, but Python, Go, Ruby, and PHP all work fine. Test vectors are byte-for-byte identical across implementations; if your signRequest output matches the published vectors, your Drive will pass Core's signature verification.
How do I test signing correctness without deploying?
Run your signRequest output against the published test vectors at /resources/drive-hmac-test-vectors. Each vector lists method, path, body, timestamp, secret, and expected signature. If your implementation produces the same hex string for the same inputs, you're done. Catches the most common bugs: wrong digest algorithm, encoding mismatches, off-by-one in the canonicalization.
What if my signing keys leak?
Rotate immediately from Merkava: Settings → Drives → [Drive name] → Rotate signing key. Old key remains valid for a 24-hour overlap window so traffic in flight continues to verify; after the window only the new key is honored. Drives need to handle the overlap cleanly — accept either signature during rotation. The published SDK examples handle this automatically.
What's next
- Drive Quickstart Tutorial → — end-to-end build of a runnable Drive in Node.js, including the manifest and all 7 endpoints.
- Platform Contract reference → — the canonical signing spec, error semantics, and version negotiation.
- Drive Author spec → — full Drive author guide (manifest, endpoints, install handshake, events).
- Apply for developer access → — if Echo runs end-to-end and your validator passes, you're ready to submit.