02 · İmza Örnekleri
Her dilde çalışan, kopyala-yapıştır referans implementasyonları. Hepsi aynı kanonik string formatını üretir; uygun olanı projenize taşıyın.
curl + bash
#!/usr/bin/env bash
set -euo pipefail
KEY_ID="${TMK_KEY_ID:?}"
SECRET="${TMK_SECRET:?}"
METHOD="${1:-GET}"
PATH_AND_QUERY="${2:-/api/v1/partner/health}"
BODY="${3:-}"
BASE="https://api.ekspertiz.app"
PATH_ONLY="${PATH_AND_QUERY%%\?*}"
if [[ "$PATH_AND_QUERY" == *"?"* ]]; then
RAW_QUERY="${PATH_AND_QUERY#*\?}"
CANONICAL_QUERY=$(echo "$RAW_QUERY" \
| tr '&' '\n' \
| sort \
| paste -sd '&' -)
else
CANONICAL_QUERY=""
fi
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_SHA=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
CANONICAL="${METHOD}
${PATH_ONLY}
${CANONICAL_QUERY}
${TS}
${NONCE}
${BODY_SHA}"
SIG=$(printf '%s' "$CANONICAL" \
| openssl dgst -sha256 -hmac "$SECRET" -hex \
| awk '{print $2}')
curl -sS "${BASE}${PATH_AND_QUERY}" \
-X "$METHOD" \
-H "X-Trameks-Key-Id: $KEY_ID" \
-H "X-Trameks-Timestamp: $TS" \
-H "X-Trameks-Nonce: $NONCE" \
-H "X-Trameks-Signature: sha256=$SIG" \
${BODY:+-d "$BODY"} \
${BODY:+-H "Content-Type: application/json"}
Not: Bash'in
sort'u tam RFC 3986 değil — production'da Node/Python/PHP örneklerini tercih edin.
Node.js (ESM)
import crypto from "node:crypto";
const BASE = process.env.TMK_BASE ?? "https://api.ekspertiz.app";
function canonicalizeQuery(rawQuery) {
const q = rawQuery.startsWith("?") ? rawQuery.slice(1) : rawQuery;
if (!q) return "";
const pairs = [];
for (const seg of q.split("&")) {
if (!seg) continue;
const i = seg.indexOf("=");
const k = i === -1 ? seg : seg.slice(0, i);
const v = i === -1 ? "" : seg.slice(i + 1);
pairs.push([rfc3986(decodeURIComponent(k)), rfc3986(decodeURIComponent(v))]);
}
pairs.sort((a, b) =>
a[0] === b[0] ? (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) : a[0] < b[0] ? -1 : 1
);
return pairs.map(([k, v]) => `${k}=${v}`).join("&");
}
function rfc3986(s) {
return encodeURIComponent(s)
.replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)
.replace(/%[0-9a-f]{2}/g, (m) => m.toUpperCase());
}
function sha256Hex(buf) {
return crypto.createHash("sha256").update(buf).digest("hex");
}
export async function partnerRequest({ method = "GET", path, body = "" }) {
const keyId = process.env.TMK_KEY_ID;
const secret = process.env.TMK_SECRET;
if (!keyId || !secret) throw new Error("TMK_KEY_ID / TMK_SECRET env yok");
const qIdx = path.indexOf("?");
const pathOnly = qIdx === -1 ? path : path.slice(0, qIdx);
const queryRaw = qIdx === -1 ? "" : path.slice(qIdx + 1);
const ts = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = sha256Hex(body);
const canonical = [
method.toUpperCase(),
pathOnly,
canonicalizeQuery(queryRaw),
ts,
nonce,
bodyHash,
].join("\n");
const signature =
"sha256=" +
crypto.createHmac("sha256", secret).update(canonical).digest("hex");
const res = await fetch(`${BASE}${path}`, {
method,
headers: {
"X-Trameks-Key-Id": keyId,
"X-Trameks-Timestamp": ts,
"X-Trameks-Nonce": nonce,
"X-Trameks-Signature": signature,
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body || undefined,
});
return { status: res.status, body: await res.json(), headers: res.headers };
}
// Kullanım:
const r = await partnerRequest({ path: "/api/v1/partner/health" });
console.log(r.status, r.body);
Python (requests)
import hashlib
import hmac
import os
import secrets
import time
import urllib.parse
import requests
BASE = os.environ.get("TMK_BASE", "https://api.ekspertiz.app")
def rfc3986(s: str) -> str:
return urllib.parse.quote(s, safe="-._~")
def canonicalize_query(raw: str) -> str:
if not raw:
return ""
if raw.startswith("?"):
raw = raw[1:]
pairs = []
for seg in raw.split("&"):
if not seg:
continue
if "=" in seg:
k, v = seg.split("=", 1)
else:
k, v = seg, ""
k = rfc3986(urllib.parse.unquote(k))
v = rfc3986(urllib.parse.unquote(v))
pairs.append((k, v))
pairs.sort()
return "&".join(f"{k}={v}" for k, v in pairs)
def sha256_hex(b: bytes) -> str:
return hashlib.sha256(b).hexdigest()
def partner_request(method: str, path: str, body: str = "") -> requests.Response:
key_id = os.environ["TMK_KEY_ID"]
secret = os.environ["TMK_SECRET"]
if "?" in path:
path_only, raw_query = path.split("?", 1)
else:
path_only, raw_query = path, ""
ts = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = sha256_hex(body.encode("utf-8"))
canonical = "\n".join([
method.upper(),
path_only,
canonicalize_query(raw_query),
ts,
nonce,
body_hash,
])
sig = hmac.new(
secret.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
headers = {
"X-Trameks-Key-Id": key_id,
"X-Trameks-Timestamp": ts,
"X-Trameks-Nonce": nonce,
"X-Trameks-Signature": f"sha256={sig}",
}
if body:
headers["Content-Type"] = "application/json"
return requests.request(
method,
f"{BASE}{path}",
headers=headers,
data=body or None,
timeout=30,
)
# Kullanım:
r = partner_request("GET", "/api/v1/partner/health")
print(r.status_code, r.json())
PHP
<?php
function rfc3986(string $s): string {
return strtr(rawurlencode($s), ['%7E' => '~']);
}
function canonicalizeQuery(string $raw): string {
if ($raw === '' || $raw === '?') return '';
if ($raw[0] === '?') $raw = substr($raw, 1);
$pairs = [];
foreach (explode('&', $raw) as $seg) {
if ($seg === '') continue;
$eq = strpos($seg, '=');
if ($eq === false) {
$k = $seg; $v = '';
} else {
$k = substr($seg, 0, $eq);
$v = substr($seg, $eq + 1);
}
$pairs[] = [rfc3986(urldecode($k)), rfc3986(urldecode($v))];
}
usort($pairs, fn($a, $b) =>
$a[0] === $b[0] ? strcmp($a[1], $b[1]) : strcmp($a[0], $b[0])
);
return implode('&', array_map(fn($p) => "{$p[0]}={$p[1]}", $pairs));
}
function partnerRequest(string $method, string $path, string $body = ''): array {
$base = getenv('TMK_BASE') ?: 'https://api.ekspertiz.app';
$keyId = getenv('TMK_KEY_ID');
$secret = getenv('TMK_SECRET');
if (!$keyId || !$secret) {
throw new RuntimeException('TMK_KEY_ID / TMK_SECRET env yok');
}
$qIdx = strpos($path, '?');
$pathOnly = $qIdx === false ? $path : substr($path, 0, $qIdx);
$queryRaw = $qIdx === false ? '' : substr($path, $qIdx + 1);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$bodyHash = hash('sha256', $body);
$canonical = implode("\n", [
strtoupper($method),
$pathOnly,
canonicalizeQuery($queryRaw),
$ts,
$nonce,
$bodyHash,
]);
$sig = 'sha256=' . hash_hmac('sha256', $canonical, $secret);
$headers = [
"X-Trameks-Key-Id: $keyId",
"X-Trameks-Timestamp: $ts",
"X-Trameks-Nonce: $nonce",
"X-Trameks-Signature: $sig",
];
if ($body !== '') $headers[] = 'Content-Type: application/json';
$ch = curl_init("$base$path");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
CURLOPT_POSTFIELDS => $body !== '' ? $body : null,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['status' => $status, 'body' => json_decode($body, true)];
}
// Kullanım:
$r = partnerRequest('GET', '/api/v1/partner/health');
print_r($r);
Yaygın hatalar
| Belirti | Sebep |
|---|---|
invalid_signature her seferinde | Body byte'ı serialize ederken farklılaşıyor; HTTP client'ın gönderdiği byte'ı hash'leyin |
expired_timestamp | Sunucu saati NTP ile senkronize değil |
invalid_signature query'li endpoint'lerde | Query canonicalize doğru değil (sıralama / encoding) |
replay_detected retry'da | Nonce'u retry'da yeniden üretin, asla aynı nonce'u tekrar göndermeyin |
Sonraki: 03 · Rate limit.