İmzaları doğrulama
Her teslimat bir Ciqra-Signature header’ı taşır. İmzası doğrulanmayan her isteği reddedin —
endpoint’iniz herkese açıktır ve ona herkes POST atabilir.
Header
Bölüm başlığı “Header”Ciqra-Signature: t=1758535200,v1=530e6f8f…4596| Parça | Anlamı |
|---|---|
t |
CIQRA’nın teslimatı imzaladığı Unix zamanı (saniye). |
v1 |
HMAC-SHA256(key = signing secret, message = t + "." + raw body) değerinin küçük harfli hex gösterimi. Birden fazla kez geçebilir — aşağıya bakın. |
Anahtar, kurulumunuzun imzalama secret’ının (ciqra_ss_…) UTF-8 baytlarıdır — önek dahil dizenin tamamı.
Mesaj ise zaman damgası, düz bir . ve istek gövdesinin alındığı haliyle ham (raw) baytlarıdır.
Ayrıştırmadan önce doğrulayın: ayrıştırılmış JSON’u yeniden serileştirmek baytları değiştirir ve imza tutmaz.
Rotasyon sırasında birden fazla v1
Bölüm başlığı “Rotasyon sırasında birden fazla v1”İmzalama secret’ınız döndürüldüğünde (rotate) önceki secret bir geçiş penceresi (24 saat) boyunca geçerli kalır.
Bu pencerede her teslimat, geçerli her secret için bir v1 taşır; önce güncel olan:
Ciqra-Signature: t=1758535200,v1=<yeni secret ile imza>,v1=<önceki secret ile imza>Böylece yeni secret’a istediğiniz an geçebilirsiniz: elinizde hangisi varsa o doğrular. Hesapladığınız değer
HERHANGİ bir v1 ile eşleşiyorsa teslimatı kabul edin; yalnızca ilkine ya da sonuncusuna bakmayın. Bir header en
fazla 4 imza taşır; dördüncüden sonrakileri yok sayabilirsiniz. Rotasyon dışında, eskisi gibi tam bir v1 vardır.
CIQRA bir secret’ı ele geçirildiği için emekliye ayırırsa geçiş penceresi olmaz: yalnızca yeni secret imzalar ve yeni secret size verilene kadar teslimatlar doğrulanmaz.
Adımlar
Bölüm başlığı “Adımlar”- Header’ı
,ile, her parçayı da ilk=ile bölün;t’yi ve herv1’i (en fazla 4) alın. tsaatinizden 5 dakikadan fazla uzaksa reddedin — bu, yakalanmış teslimatların yeniden oynatılmasını (replay) engeller.hex(HMAC-SHA256(secret, t + "." + rawBody))değerini hesaplayın.- Her
v1ile sabit zamanlı (constant time) karşılaştırın; herhangi biri eşleşirse kabul edin.
import { createHmac, timingSafeEqual } from "node:crypto";
// Verify a CIQRA webhook. rawBody: the request body exactly as received (string), before any JSON parsing.// The header may carry more than one v1 (during a signing-secret rotation): accept if ANY of them matches.export function verifyCiqra(rawBody, header, secret, toleranceSec = 300, nowSec = Date.now() / 1000) { let t = null; const given = []; for (const part of String(header).split(",")) { const eq = part.indexOf("="); if (eq <= 0) continue; const key = part.slice(0, eq).trim(); const value = part.slice(eq + 1).trim(); if (key === "t") t = Number(value); else if (key === "v1" && given.length < 4) given.push(value); }
if (!Number.isInteger(t) || Math.abs(nowSec - t) > toleranceSec) return false;
const expected = Buffer.from(createHmac("sha256", secret).update(`${t}.${rawBody}`, "utf8").digest("hex")); let ok = false; for (const v1 of given) { const candidate = Buffer.from(v1); if (candidate.length === expected.length && timingSafeEqual(candidate, expected)) ok = true; } return ok;}import hashlibimport hmacimport time
def verify_ciqra(raw_body: bytes, header: str, secret: str, tolerance: int = 300, now: float | None = None) -> bool: """Verify a CIQRA webhook. raw_body: the request body bytes exactly as received.
The header may carry more than one v1 (during a signing-secret rotation): accept if ANY of them matches. """ t = None given = [] for part in header.split(","): key, sep, value = part.partition("=") if not sep: continue key, value = key.strip(), value.strip() if key == "t": t = value elif key == "v1" and len(given) < 4: given.append(value) try: t = int(t) except (TypeError, ValueError): return False if abs((time.time() if now is None else now) - t) > tolerance: return False expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() ok = False for v1 in given: ok |= v1.isascii() and hmac.compare_digest(expected, v1) return okusing System.Security.Cryptography;using System.Text;
static bool VerifyCiqra(string rawBody, string header, string secret, TimeSpan tolerance){ string? t = null; var given = new List<string>(); foreach (var part in header.Split(',', StringSplitOptions.TrimEntries)) { var eq = part.IndexOf('='); if (eq <= 0) continue; if (part[..eq] == "t") t = part[(eq + 1)..]; else if (part[..eq] == "v1" && given.Count < 4) given.Add(part[(eq + 1)..]); } if (!long.TryParse(t, out var unix) || given.Count == 0) return false; if (unix < -62135596800 || unix > 253402300799) return false; // DateTimeOffset aralığı dışında
var signedAt = DateTimeOffset.FromUnixTimeSeconds(unix); if ((DateTimeOffset.UtcNow - signedAt).Duration() > tolerance) return false;
var mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes($"{unix}.{rawBody}")); var expected = Encoding.ASCII.GetBytes(Convert.ToHexStringLower(mac)); var ok = false; foreach (var v1 in given) // rotasyon sırasında iki v1 gelir; herhangi biri eşleşebilir ok |= CryptographicOperations.FixedTimeEquals(expected, Encoding.ASCII.GetBytes(v1)); return ok;}Yukarıdaki Node.js ve Python kodu, bu sitenin test takımının aşağıdaki test vektörüne karşı çalıştırdığı dosyanın ta kendisidir.
Test vektörü
Bölüm başlığı “Test vektörü”Gerçek bir mağazaya bağlanmadan önce uygulamanızı bununla kontrol edin.
| Girdi | Değer |
|---|---|
| secret | ciqra_ss_test |
| ham gövde | {"orderId":"0f8e","status":"Paid"} |
t |
1758535200 |
| imzalanan mesaj | 1758535200.{"orderId":"0f8e","status":"Paid"} |
| header | t=1758535200,v1=530e6f8fc05a8e47dd1befcbdf45fa89b16b4491362f5e7c0427b44965bb4596 |
Bu değer platformun kendi içindeki bir testle sabitlenmiştir; dolayısıyla CIQRA’nın gerçekte gönderdiğinden sapamaz. Test ederken sabit bir “şimdi” değeri vermeyi unutmayın, yoksa 5 dakikalık pencere 2025 tarihli zaman damgasını reddeder.
Diğer teslimat header’ları
Bölüm başlığı “Diğer teslimat header’ları”| Header | Değer |
|---|---|
Ciqra-Event |
Olay türü, ör. order.paid. |
Ciqra-Event-Id |
Olayın kararlı id’si — tekilleştirme için bunu kullanın. |
Ciqra-Delivery |
Bu teslimat deneme serisinin id’si. |
Ciqra-Webhook-Id |
Abonelik id’si. |
Ciqra-Event-Age-Seconds |
Olayın gerçekleşmesinden bu yana geçen saniye; yeniden denemelerden sonra büyür. |
User-Agent |
CIQRA-Webhooks/1.0 |
Bu header’lar imzalı değildir. Yönlendirme için kullanın; veri için yalnızca imzalı gövdeye güvenin.