İçeriğe geç

İmza doğrulama

Adresinizi bilen herkes ona istek gönderebilir. Bir gönderim, imzası aboneliğinizin imza sırrıyla tuttuğunda Quatlas’tandır.

  1. Quatlas-Timestamp (Unix saniyesi) ve Quatlas-Signature başlıklarını okuyun.
  2. Zaman damgası saatinizden 5 dakikadan uzaksa gönderimi reddedin. Bu, eski bir gönderimin yeniden oynatılmasını önler.
  3. <timestamp>.<ham gövde> metninin HMAC-SHA256 değerini hesaplayın: zaman damgası başlığının değeri, bir nokta, ardından gövdenin aldığınız hâliyle baytları. Anahtar, imza sırrınızın (whsec_...) UTF-8 baytlarıdır. Sonucu küçük harf hex olarak yazın.
  4. Quatlas-Signature, virgülle ayrılmış bir ya da daha fazla v1=<hex> değeri taşır. Herhangi bir v1= değeri sizinkiyle sabit sürede karşılaştırıldığında eşitse gönderimi kabul edin. Sır yenilemesinin geçiş süresinde, geçerli her sır için bir tane olmak üzere iki değer vardır.
  5. Quatlas-Event-Id ile tekilleştirin: aynı olay birden fazla gelebilir.

Aşağıdaki her işlev ham gövdeyi, iki başlığı ve sırrınızı alır, gönderimin gerçek olup olmadığını döner. Platformun imzaladığı şekilde imzalanmış gönderimlerle test edilmiştir; yenilemedeki iki imza ve 5 dakikalık pencere dahil.

verify.mjs
import crypto from "node:crypto";
const TOLERANCE_SECONDS = 300; // refuse deliveries signed more than 5 minutes from your clock
/**
* Verifies a Quatlas webhook delivery.
* rawBody: the request body as a Buffer, exactly as received (not re-serialised JSON).
* headers: the request headers, lower-cased names (as Node gives them).
* secret: the subscription's signing secret (whsec_...).
*/
export function verifyQuatlasSignature(rawBody, headers, secret) {
const timestamp = headers["quatlas-timestamp"];
const signature = headers["quatlas-signature"];
if (typeof timestamp !== "string" || typeof signature !== "string" || !/^\d+$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;
const expected = Buffer.from(
crypto.createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest("hex"),
);
// One v1= value per valid secret (two during a secret rotation): accept when any one matches.
return signature.split(",").some((part) => {
const value = part.trim();
if (!value.startsWith("v1=")) return false;
const candidate = Buffer.from(value.slice(3));
return candidate.length === expected.length && crypto.timingSafeEqual(candidate, expected);
});
}

Express’te ham gövdeyi yalnız bu rota için alın:

import express from "express";
import { verifyQuatlasSignature } from "./verify.mjs";
const app = express();
app.post("/quatlas/webhooks", express.raw({ type: "application/json" }), (req, res) => {
if (!verifyQuatlasSignature(req.body, req.headers, process.env.QUATLAS_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body.toString("utf8"));
// event.id daha önce işlendiyse atlayın; işi kuyruğa koyun, sonra yanıt verin.
res.sendStatus(204);
});

Sır whsec_test, zaman damgası 1791963721 ve gövde {"id":"evt"} ise imzalanan metin şudur:

1791963721.{"id":"evt"}

ve başlık şudur:

Quatlas-Signature: v1=878fa619bb541a51b45e860b0d129b198853c2768f6a15d2d020a9f1e5b2b459

Kendi kodunuzu denetlemek için OpenSSL ile üretin. (Tam bir denetim, saatiniz bu sabit örneğin 5 dakika yakınında değilse onu zaman damgası yüzünden yine reddeder.)

Terminal window
printf '%s' '1791963721.{"id":"evt"}' | openssl dgst -sha256 -hmac 'whsec_test'
# SHA2-256(stdin)= 878fa619bb541a51b45e860b0d129b198853c2768f6a15d2d020a9f1e5b2b459

4xx ile yanıt verin ve gönderimi işlemeyin. Quatlas 2xx dışındaki her yanıtı başarısızlık sayar ve takvimine göre yeniden dener; gerçek gönderimleri sürekli reddeden bir alıcının (örneğin yenilenen sır devreye alınmadıysa) aboneliği art arda 20 başarısız denemeden sonra kapanır. Bkz. Webhook’lar.

Sırayla denetleyin: ham baytları mı imzalıyorsunuz; güncel sırrı mı (yenileme sırasında öncekini mi) kullanıyorsunuz; saatiniz doğru mu (NTP kullanın); küçük harf hex mi karşılaştırıyorsunuz.