Serve HMAC

Serve HMAC

SDKs authenticate to the adserver with the public app key prefix plus HMAC-SHA256. Production and sandbox keys sign the same way. GET /v1/click/{token} does not use these headers.

POSThttps://serve.meridianads.tech/v1/decision

Local http://localhost:8090/v1/decision

Headers

HeaderValue
X-App-Key Public prefix from key create, for example mk_live_… or mk_test_…
X-TimestampUnix seconds. Default allowed skew is 300 seconds.
X-SignatureHex digest of HMAC-SHA256 over the canonical string.

Canonical string

Sign exactly {unix_timestamp}.{METHOD}.{path}.{raw_body}. METHOD is the HTTP method as sent (POST). path is the URL path only, such as /v1/decision, with no query string. raw_body is the exact bytes on the wire. If JSON spacing differs from what you signed, the signature fails.

The secret returned at create time is hex. Decode it to 32 bytes before HMAC. Do not HMAC the hex string itself.

Node

Node
import { createHmac } from 'node:crypto'

const ts = String(Math.floor(Date.now() / 1000))
const method = 'POST'
const path = '/v1/decision'
const body = JSON.stringify({
  placement: 'home',
  user_key: 'user-42',
  context: { country: 'US' }
})
const canonical = `${ts}.${method}.${path}.${body}`
const secret = Buffer.from(secretHex, 'hex')
const signature = createHmac('sha256', secret).update(canonical).digest('hex')

await fetch('https://serve.meridianads.tech/v1/decision', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-App-Key': prefix,
    'X-Timestamp': ts,
    'X-Signature': signature
  },
  body
})

Python

Python
import hashlib, hmac, json, time, urllib.request

ts = str(int(time.time()))
method = "POST"
path = "/v1/decision"
body = json.dumps(
    {"placement": "home", "user_key": "user-42", "context": {"country": "US"}},
    separators=(",", ":"),
)
canonical = f"{ts}.{method}.{path}.{body}"
secret = bytes.fromhex(secret_hex)
signature = hmac.new(secret, canonical.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(
    "https://serve.meridianads.tech/v1/decision",
    data=body.encode(),
    method=method,
    headers={
        "Content-Type": "application/json",
        "X-App-Key": prefix,
        "X-Timestamp": ts,
        "X-Signature": signature,
    },
)