Безпека
Translator note: machine-translated baseline. Needs human review by a native UK speaker. EN source is canonical.
casinocrm.io використовує HMAC-SHA256 для webhook автентифікації на обох ногах (inbound + outbound). Та сама схема, що Stripe / GitHub — ймовірно, ваша команда вже знайома.
Signature scheme
signature = hex(HMAC_SHA256(secret, "<timestamp>.<body>"))
timestamp— Unix секунди (string)body— raw request body bytes (НЕ re-serialised JSON; підпишіть рівно те, що йде по дроту)secret— спільний secret, обмін out-of-band
Headers, які casinocrm.io надсилає + очікує:
X-casinocrm.io-Signature: <hex-hmac-sha256>
X-casinocrm.io-Timestamp: <unix-seconds>
casinocrm.io також приймає префікс v1=<hex> на signature (Stripe-сумісне).
Anti-replay
Відмовляйте подіям з timestamp поза [now - 5 min, now + 5 min]. casinocrm.io enforce-ить це н а inbound; рекомендуємо те саме на ваших endpoints.
Constant-time порівняння
Завжди порівнюйте signatures з constant-time equality (наприклад, Node crypto.timingSafeEqual, Go subtle.ConstantTimeCompare). Наївне == є timing-leakable.
Reference реалізації
Node.js / TypeScript
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody: string, header: string, ts: string, secret: string): boolean {
// Anti-replay
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
// Compute expected
const expected = createHmac("sha256", secret)
.update(`${ts}.${rawBody}`, "utf8")
.digest("hex");
// Дозволити v1= префікс
const provided = header.replace(/^v1=/, "");
if (provided.length !== expected.length) return false;
try {
return timingSafeEqual(Buffer.from(provided, "hex"), Buffer.from(expected, "hex"));
} catch {
return false;
}
}
Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
func verify(rawBody []byte, header, ts, secret string) bool {
tsNum, err := strconv.ParseInt(ts, 10, 64)
if err != nil {
return false
}
if abs(time.Now().Unix()-tsNum) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(ts + "." + string(rawBody)))
expected := hex.EncodeToString(mac.Sum(nil))
provided := strings.TrimPrefix(header, "v1=")
return hmac.Equal([]byte(provided), []byte(expected))
}
func abs(x int64) int64 { if x < 0 { return -x }; return x }
Python
import hmac
import hashlib
import time
def verify(raw_body: bytes, header: str, ts: str, secret: str) -> bool:
try:
ts_num = int(ts)
except ValueError:
return False
if abs(time.time() - ts_num) > 300:
return False
expected = hmac.new(
secret.encode("utf-8"),
f"{ts}.{raw_body.decode('utf-8')}".encode("utf-8"),
hashlib.sha256,
).hexdigest()
provided = header.removeprefix("v1=")
return hmac.compare_digest(expected, provided)
Idempotency
Окрім signature verification, inbound webhook receiver дедуплікує по (adapter_slug, event_id) через webhook_events_processed ledger. Replays повертають 200 { applied: false, replay: true } миттєво.
Ви також маєте дедуплікувати на своєму боці — casinocrm.io може retry-ити outbound виклик (наприклад, на 504 з вашого боку, який насправді успішний). Використайте grant_id від casinocrm.io (або який correlation id ми передали) як dedup key на вашому боці.
Rotation secrets
Координуйте з командою casinocrm.io через Slack / email. Rotation flow:
- Згенеруйте новий secret на обох сторонах
- casinocrm.io приймає ОБИДВА старий + новий протягом 24h overlap window (per-adapter env var:
casinocrm.io_ADAPTER_<SLUG>_WEBHOOK_SECRET_NEXT) - Після overlap старий secret скиньте
- Перевірте метрики (немає сплеску в
signature mismatchпомилках)
Rotation cadence: щонайменше щорічно, або негайно при підозрі на leak.
URL allowlist
Outbound webhooks від casinocrm.io (journey call_webhook вузли) — HTTPS-only. Відмовляємось надсилати на plain HTTP URLs навіть у dev — завжди шифруйте journey context у транзиті.
API key rotation
Project API keys можна rotate з admin: Settings → API keys → Revoke + Create new. casinocrm.io приймає обидва ключі протягом конфігурованого overlap window, тож можна rolling deploy без downtime.