Bir webhook imzasını doğrulamak için, gönderenin imzaladığı baytların tam olarak üzerinden, onunla paylaştığınız gizli anahtarla bir HMAC'i yeniden hesaplayın ve değerinizi imza üstbilgisiyle sabit zamanlı karşılaştırın. Sonra imzalanan zaman damgasının yeni olduğunu kontrol edin. Elido webhook'ları için bu, tüm whsec_... gizli anahtarınızla anahtarlanmış, {X-Webhook-Timestamp}.{raw body} üzerinden HMAC-SHA256 demektir; onaltılık kodlanır, v1= ile öneklenir ve X-Elido-Signature ile eşleştirilir.
Algoritmanın tamamı bu. Başarısızlıklar çevresindeki ayrıntılardan gelir: çok erken çalışan bir gövde ayrıştırıcısı, çözülmüş bir gizli anahtar, base64 bir özetle karşılaştırılan onaltılık bir özet. Bu yazı önce genel olarak HMAC webhook imza doğrulamasını, sonra Node, Python, Go ve bir n8n Code düğümü için çalışan Elido kodunu, ayrıca çoğu hızlı başlangıç rehberinin dışarıda bıraktığı tekrar penceresini ve gizli anahtar döndürmeyi kapsar.
Henüz bir uç nokta kurmadıysanız, her olay türünü ve yük zarfını listeleyen bağlantı olayları için webhook'lar yazısıyla başlayın. Bu sayfa, imzalı bir istek sunucunuza ulaştığı yerden devam eder.
HMAC Webhook İmzaları Nasıl Çalışır
Bir webhook uç noktası herkese açık bir URL'dir. Onu bulan herkes gerçek bir olay gibi görünen bir JSON gövdesi POST edebilir; bu yüzden alıcının kaynak kanıtına ihtiyacı vardır. HMAC bunu ucuza sağlar: gönderen ve alıcı bir gizli anahtarı paylaşır, gönderen HMAC-SHA256(secret, message) hesaplar ve sonucu bir üstbilgiye koyar, alıcı da aynı hesabı yapıp karşılaştırır. Gizli anahtar olmadan kimse eşleşen bir değer üretemez ve iletinin tek bir baytını değiştirmek tüm özeti değiştirir.
Sağlayıcılar arasında üç ayrıntı farklıdır ve her biri yanlış yapılırsa doğrulamayı bozar:
- İletiye ne girer. GitHub yalnızca ham gövdeyi imzalar. Stripe ve Elido bir zaman damgasını, bir noktayı ve gövdeyi imzalar. Standard Webhooks belirtimi bir ileti kimliğini, zaman damgasını ve gövdeyi imzalar.
- Özet nasıl kodlanır. Onaltılık ya da base64;
v1=veyasha256=gibi bir şema önekiyle. - Anahtar nedir. Bazı sağlayıcılar önekinden sonra gizli anahtarı base64 ile çözer. Elido çözmez: anahtar, UTF-8 baytları olarak tam
whsec_dizesidir.
Zaman damgasını imzalanan iletinin içine koymak önemlidir. Bir saldırganın eski, geçerli imzalı bir gövdeyi yeni bir zaman damgası üstbilgisiyle eşleştirmesini engeller; bir tekrar penceresini uygulanabilir kılan da budur.
Elido Neyi İmzalar ve Hangi Üstbilgiler Taşır
event ya da siem uç noktasına yapılan her teslimat, Content-Type: application/json ile ve {"type", "workspace_id", "data", "timestamp"} biçiminde bir gövdeyle yapılan bir POST'tur. Sohbet biçimli uç nokta türleri (Discord, Telegram, Sentry) URL'leri üzerinden kimlik doğrular ve HMAC üstbilgisi taşımaz; dolayısıyla aşağıdakilerin hepsi yalnızca ilk iki türe uygulanır.
| Üstbilgi | Değer | Onunla ne yapılır |
|---|---|---|
X-Elido-Signature | v1= + 64 küçük harfli onaltılık karakter | Hesapladığınız değerle karşılaştırın |
X-Webhook-Signature | Yukarıdakiyle aynı değer | Eski takma ad; ikisinden birini okuyun, ikisini değil |
X-Webhook-Timestamp | Unix saniyesi, örneğin 1789000000 | İmzalanan iletinin parçası; yaşını kontrol edin |
X-Elido-Signature-Previous | v1= + onaltılık, eski gizli anahtarla imzalı | Yalnızca bir döndürme tolerans penceresinde var |
X-Webhook-Event | Olay adı, örneğin link.created | Olayı yönlendirin (doğruladıktan sonra) |
X-Webhook-Delivery | Sayısal teslimat kimliği, yeniden denemelerde sabit | Tekrarsız işleme için tekilleştirme anahtarı |
Gizli anahtarı oluşturma anında alırsınız: whsec_ ve ardından 64 onaltılık karakter. Oluşturma yanıtında bir kez döner ve bir daha dönmez; bu yüzden doğrudan gizli anahtar deponuza gider.
Kodunuzu karşısında çalıştırabileceğiniz bir test vektörü: whsec_test_only_do_not_use gizli anahtarı, 1789000000 zaman damgası ve {"type":"link.created","workspace_id":42} gövdesiyle doğru üstbilgi değeri şudur:
v1=b9369aa411a8b7ce705bcd5bba112dea9d72d2e787aa88959ff62f33942d1a15
Bunu bir kabuktan yeniden üretebilirsiniz; bir alıcı göndericiyle aynı fikirde olmadığında ilk hamlem budur:
printf '%s.%s' 1789000000 '{"type":"link.created","workspace_id":42}' \
| openssl dgst -sha256 -hmac 'whsec_test_only_do_not_use' -r
Burada bir tuzak var: gövdenin kendi timestamp alanı olayın gerçekleştiği zamandır; imzalanan zaman damgası ise istek gönderilirken ayarlanan X-Webhook-Timestamp üstbilgisindekidir. Bunları karıştırmayın.
Node'da Webhook İmzasını Doğrulama
Express'te çoğu başarısızlığın çözümü tek satırdır: req.body alınan tam baytların bir Buffer'ı olsun diye webhook rotasına express.raw() bağlayın. Bu rotayı herhangi bir genel app.use(express.json()) çağrısından önce kaydedin; çünkü JSON ayrıştırıcı akışı tükettikten sonra ham ayrıştırıcının okuyacak bir şeyi kalmaz.
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
const SECRET = process.env.ELIDO_WEBHOOK_SECRET; // the full whsec_... string
const TOLERANCE_SEC = 300;
function matches(expected, got) {
const a = Buffer.from(expected);
const b = Buffer.from(got ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}
const app = express();
app.post(
"/webhooks/elido",
express.raw({ type: "application/json" }),
(req, res) => {
const ts = req.get("X-Webhook-Timestamp") ?? "";
if (
!/^\d+$/.test(ts) ||
Math.abs(Date.now() / 1000 - Number(ts)) > TOLERANCE_SEC
) {
return res.status(400).send("stale or missing timestamp");
}
const expected =
"v1=" +
createHmac("sha256", SECRET)
.update(`${ts}.`)
.update(req.body)
.digest("hex");
const ok = [
req.get("X-Elido-Signature"),
req.get("X-Elido-Signature-Previous"),
].some((got) => matches(expected, got));
if (!ok) return res.status(401).send("bad signature");
const event = JSON.parse(req.body.toString("utf8"));
// enqueue event, then acknowledge fast
res.sendStatus(200);
},
);
Uzunluk kontrolü süs değildir. timingSafeEqual, farklı uzunluktaki tamponlarda false döndürmek yerine hata fırlatır. API ve SDK hızlı başlangıcındaki TypeScript SDK'sı henüz npm'de değil, bu yüzden karşılaştırmayı yukarıdaki gibi kendiniz yazın.
Python ve Go'da Webhook HMAC SHA256 Doğrulaması
Python'ın standart kütüphanesi bunu karşılar. FastAPI ile await request.body() ham baytları döndürür; Flask'ta request.json'a herhangi bir şey dokunmadan önce request.get_data() çağırın.
import hashlib
import hmac
import json
import os
import time
from fastapi import FastAPI, HTTPException, Request
SECRET = os.environ["ELIDO_WEBHOOK_SECRET"].strip().encode()
TOLERANCE = 300
app = FastAPI()
def verify(raw: bytes, ts: str, candidates: list) -> bool:
if not ts.isdigit() or abs(time.time() - int(ts)) > TOLERANCE:
return False
digest = hmac.new(SECRET, ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
expected = "v1=" + digest
return any(c and hmac.compare_digest(c, expected) for c in candidates)
@app.post("/webhooks/elido")
async def elido_webhook(request: Request):
raw = await request.body()
h = request.headers
sigs = [h.get("x-elido-signature"), h.get("x-elido-signature-previous")]
if not verify(raw, h.get("x-webhook-timestamp", ""), sigs):
raise HTTPException(status_code=401, detail="bad signature")
event = json.loads(raw)
# enqueue event
return {"ok": True}
Go'da gövdeyi bir kez okuyun, boyutunu sınırlayın ve crypto/hmac içindeki, sabit zamanlı karşılaştıran hmac.Equal işlevini kullanın. İletiyi, ayrıştırılmış tamsayıyı yeniden biçimlendirmek yerine, üstbilgi dizesinden tam alındığı gibi kuruyorum.
package webhook
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"strconv"
"time"
)
const toleranceSec = 300
// Verify returns the raw body when the request carries a valid Elido signature.
func Verify(w http.ResponseWriter, r *http.Request, secret []byte) ([]byte, bool) {
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
if err != nil {
return nil, false
}
tsHeader := r.Header.Get("X-Webhook-Timestamp")
ts, err := strconv.ParseInt(tsHeader, 10, 64)
if err != nil {
return nil, false
}
if age := time.Now().Unix() - ts; age > toleranceSec || age < -toleranceSec {
return nil, false
}
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(tsHeader + "."))
mac.Write(body)
expected := []byte("v1=" + hex.EncodeToString(mac.Sum(nil)))
for _, name := range []string{"X-Elido-Signature", "X-Elido-Signature-Previous"} {
if got := r.Header.Get(name); got != "" && hmac.Equal([]byte(got), expected) {
return body, true
}
}
return nil, false
}
Üç sürüm de kötü imzada 401 yanıtlar ve JSON'u yalnızca kontrol geçtikten sonra ayrıştırır. Elido, 2xx olmayan her yanıtı başarısız deneme sayar; bu yüzden doğrulayıcınız yanlışsa gerçek teslimatlar uç noktanın teslimat günlüğünde 401 olarak birikir. Bir dağıtımdan sonra bakılacak ilk yer burasıdır.
Bunu canlı trafikle görmek ister misiniz? Webhook'lar özellik sayfasından bir çalışma alanında uç nokta oluşturun ve onu yerel bir tünele yönlendirin; teslimat günlüğü her deneme için doğrulayıcınızın döndürdüğü durum kodunu gösterir.
Bir n8n Code Düğümünde Webhook İmzasını Doğrulama
n8n aynı kontrolü ek bir hizmet olmadan yapabilir. Webhook düğümünde, dokunulmamış isteği ikili veri olarak saklayan Raw Body seçeneğini açın, sonra hemen ardına bir Code düğümü ekleyin. Yerleşik crypto modülüne n8n Code düğümünde izin verilir; bu yüzden olduğu gibi çalışır:
const crypto = require("crypto");
const h = $input.first().json.headers;
const raw = await this.helpers.getBinaryDataBuffer(0, "data");
const ts = h["x-webhook-timestamp"] ?? "";
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
throw new Error("stale delivery");
}
const expected = Buffer.from(
"v1=" +
crypto
.createHmac("sha256", $env.ELIDO_WEBHOOK_SECRET)
.update(`${ts}.`)
.update(raw)
.digest("hex"),
);
const ok = ["x-elido-signature", "x-elido-signature-previous"].some((name) => {
const got = Buffer.from(h[name] ?? "");
return (
got.length === expected.length && crypto.timingSafeEqual(got, expected)
);
});
if (!ok) throw new Error("bad signature");
return [{ json: JSON.parse(raw.toString("utf8")) }];
Fırlatılan bir hata yürütmeyi durdurur; bu yüzden sahte bir olay üzerinde aşağı akışta hiçbir şey çalışmaz. Kendi sunucunuzda barındırmaya özgü bir tuzak: örneğiniz N8N_BLOCK_ENV_ACCESS_IN_NODE=true ayarlıysa Code düğümü $env okuyamaz, gizli anahtar boş döner ve her teslimat kontrolü geçemez. Ters vekil tarafını kendi sunucunuzda barındırılan n8n rehberi kapsar; olaylar gelmeye başladıktan sonra ne inşa edeceğinizi ise n8n URL kısaltıcı yazısı gösterir.
Elido, Stripe, GitHub ve Standard Webhooks'tan Nasıl Ayrılır
Başka bir sağlayıcı için bir doğrulayıcı yazdıysanız onu körü körüne kopyalamayın. Her satıcının belgelerine karşı 2026-10-25 tarihinde kontrol edildi:
- Stripe,
t=ve bir ya da daha fazlav1=değerini tek birStripe-Signatureüstbilgisine koyar,timestamp.bodyiletisini onaltılık HMAC-SHA256 ile imzalar ve kütüphaneleri varsayılan olarak beş dakikalık tolerans kullanır (Stripe belgeleri). Elido zaman damgasını kendi üstbilgisine ayırır. - GitHub, yalnızca ham gövdeyi imzalar,
X-Hub-Signature-256içindesha256=artı onaltılık gönderir ve sabit zamanlı karşılaştırma kullanmanızı söyler (GitHub belgeleri). Kontrol edilecek zaman damgası yoktur. - Standard Webhooks,
webhook-id,webhook-timestampvewebhook-signaturekullanır,id.timestamp.bodyiletisini imzalar,v1,önekiyle base64 kodlar ve tolerans değerini size bırakır (belirtim). Elido'nunX-Webhook-Deliveryüstbilgisi kimlik rolünü oynar, ama imzalanan iletinin parçası değildir.
Elido'nun kendi şeması api-core'daki signing.Sign içindeki şemadır: ileti Unix zaman damgası, bir nokta ve gövdedir; anahtar, saklandığı hâliyle gizli anahtar dizesidir; sonuç v1= artı küçük harfli onaltılıktır. Elido'nun link.clicked olayı yoktur; bu yüzden bu kanalda tıklama düzeyinde teslimat beklemeyin.
Tekrar Pencereleri ve Gizli Anahtar Döndürme
Geçerli bir imza bir isteği kimin gönderdiğini kanıtlar, ne zaman gönderdiğini değil. İmzalı bir teslimatı bir günlük satırından ya da yanlış yapılandırılmış bir vekil sunucudan yakalayan biri onu bir hafta sonra yeniden gönderebilir ve HMAC yine eşleşir. Zaman damgası kontrolü bu boşluğu kapatır ve bu sizin işinizdir: Elido'nun teslimat işçisi zaman damgasını imzalar ama sizin tarafınızda hiçbir pencere uygulamaz. Ben 300 saniye kullanıyorum. Alıcının saatini NTP ile senkron tutun; çünkü birkaç dakika sapan bir sunucu dürüst trafiği reddetmeye başlar.
Yeniden denemeler pencereye takılmaz. Her deneme yeni bir X-Webhook-Timestamp ve yeni bir imza alırken X-Webhook-Delivery aynı kalır. Bu ayrım size iki savunma verir: zaman damgası, yakalanan bir isteğin ne kadar süre kullanılabilir kalacağını sınırlar ve teslimat kimliği üzerindeki benzersiz bir dizin, meşru bir yeniden denemenin iki kez işlenmesini önler. Hız sınırları ve idempotency yazısı aynı deseni gelen API tarafında kapsar.
Döndürme POST /v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret ile ya da uç nokta sayfasındaki Döndür düğmesiyle çalışır. Yanıt, yeni gizli anahtarı bir kez ve grace_window_days (7) ile previous_expires_at değerlerini içerir. Bu yedi gün boyunca her teslimat iki imza taşır:
X-Elido-Signature, yeni gizli anahtarla yapılmışX-Elido-Signature-Previous, eskisiyle yapılmış
Yukarıdaki her kod parçasının, elinde tuttuğu tek gizli anahtara karşı iki üstbilgiyi de kontrol etmesinin nedeni budur. Hâlâ eski gizli anahtarı çalıştıran bir alıcı ikinci üstbilgiyle eşleşir; yenisini dağıttıktan sonra ilkiyle eşleşir. Arada hiçbir şey başarısız olmaz. Yine de önceki anahtar üstbilgisini bir garanti değil, bir köprü olarak ele alın ve yeni gizli anahtarı haftanın başında dağıtın.
Webhook İmza Doğrulaması Neden Başarısız Olur
Birine bunu hata ayıklamasında yardım ettiğimde hemen her seferinde aynı kısa liste çıkıyor. Sırayla gidin:
- Yeniden serileştirilmiş JSON.
JSON.stringify(req.body)ya dajson.dumps(payload), göndericinin özetlediğinden farklı baytlar üretir: anahtar sırası, boşluklar, kaçırılmış eğik çizgiler, unicode kaçışları. Ham gövdeyi özetleyin. Çerçeveniz onu zaten ayrıştırdıysa, dizeyi yeniden kurmaya çalışmak yerine ara katman yazılımı sırasını düzeltin. - Yanlış anahtar baytları. Elido, HMAC anahtarı olarak tüm
whsec_...dizesini kullanır. Önekini çıkarmak, kalanı onaltılıktan çözmek ya da base64'ten çözmek (Standard Webhooks kütüphanelerinin yaptığı) size farklı bir anahtar verir. Bir gizli anahtar dosyasınaechoile yazılan sondaki satır sonu da aynısını yapar; Python örneğinin.strip()çağırmasının nedeni budur. - Kodlama uyuşmazlığı.
v1=artı küçük harfli onaltılığı üstbilgiyle karşılaştırın. Base64 bir özet, büyük harfli bir onaltılık dize ya da eksik bir önek asla eşleşmez. - Yanlış zaman damgası. Gövdenin
timestampalanını değil,X-Webhook-Timestampüstbilgi dizesini kullanın; ayrıştırıp yeniden biçimlendirdiğiniz bir sayıyı da değil.
İki küçük nokta: == ile karşılaştırmak çalışır ama zamanlamayı sızdırır; bu yüzden dilinizin sunduğu sabit zamanlı işlevi kullanın; ve gövdeleri açan ya da yeniden kodlayan bir vekil sunucu da doğrulamayı bozar, ancak bu düz JSON POST'larında nadirdir.
İki taraf hâlâ anlaşamıyorsa, zaman damgasını, gövde uzunluğunu ve özetinizin ilk birkaç onaltılık karakterini günlüğe yazın, sonra aynı girdilerde daha önceki openssl satırını çalıştırın. Hangi taraf openssl ile eşleşiyorsa doğru olan odur. İmzalamanın, herhangi bir sağlayıcıda kontrol etmeye değer diğer denetimler arasında nereye oturduğu için URL kısaltıcı güvenlik kontrol listesine bakın.
Temel yazıyı okuyun: bağlantı olayları için webhook'lar.
Blogda İlgili Yazılar
- Bağlantı olayları için webhook'lar - olay türleri, yük zarfı ve yeniden deneme politikası.
- Tıklama takibi için webhook'lar ve yoklama karşılaştırması - itmenin çekmeyi ne zaman yendiği ve ne zaman yenmediği.
- n8n ile kendi sunucunuzda barındırılan bağlantı otomasyonu - ters vekil, kuyruk modu ve imza kontrolleri tek bir yığında.
- URL kısaltıcı API: hız sınırları, yeniden denemeler, idempotency - diğer yönden tekilleştirme desenleri.
- URL kısaltıcı güvenlik kontrol listesi - herhangi bir sağlayıcıda doğrulanacak dokuz denetim.
Sıkça sorulan sorular
Bir webhook imzasını nasıl doğrularım?
Paylaşılan gizli anahtarı kullanarak, gönderenin imzaladığı şeyin tam olarak üzerinden HMAC'i yeniden hesaplayın ve sonucunuzu imza üstbilgisiyle sabit zamanlı karşılaştırın. Elido için bu, X-Webhook-Timestamp değeri, bir nokta ve ham gövde üzerinden HMAC-SHA256 demektir; onaltılık kodlanır ve v1= öneki alır. Hiçbir şey eşleşmezse ya da zaman damgası eskiyse isteği reddedin.
Webhook imza doğrulamam neden sürekli başarısız oluyor?
Hemen her zaman, gönderenden farklı baytları özetlediğiniz için. Önce bir JSON gövde ayrıştırıcısı çalıştı ve nesneyi yeniden serileştirdiniz, ya da gizli anahtarı çözdünüz, ya da onaltılığı base64 ile karşılaştırdınız. Ham istek baytlarını özetleyin, gizli anahtar dizesini tam verildiği gibi kullanın ve iki değeri yan yana günlüğe yazın.
Bir webhook'ta HMAC nedir?
HMAC, anahtarlı bir özettir: gönderen, sizinle paylaştığı bir gizli anahtarı iletinin SHA-256 özetine karıştırır. Yalnızca gizli anahtarı elinde tutan biri eşleşen bir değer üretebilir; bu yüzden geçerli bir imza, isteğin gönderenden geldiğini ve gövdenin yolda değiştirilmediğini kanıtlar.
Webhook tekrar saldırılarını nasıl önlerim?
Zaman damgasını gövdeyle birlikte imzalayın ve zaman damgası birkaç dakikadan eski olan her isteği reddedin; beş dakika yaygın bir penceredir. Sonra teslimat kimliğini saklayın ve zaten işlediğiniz kimlikleri atlayın. Elido zaman damgasını imzalar ama tazelik kontrolünü alıcınıza bırakır.
HMAC-SHA256 webhook imzası için onaltılık mı yoksa base64 mü kullanmalıyım?
Gönderen neyi belgeliyorsa; çünkü aynı özetin iki kodlaması asla eşit çıkmaz. Elido, v1= önekinden sonra küçük harfli onaltılık gönderir. Shopify ve Standard Webhooks belirtimi base64, GitHub ise sha256= sonrasında onaltılık kullanır. Karşılaştırmadan önce özetinizi aynı biçimde kodlayın.
Olay kaybetmeden bir webhook gizli anahtarını nasıl döndürürüm?
Bir süre iki anahtarla da imzalayan bir gönderen kullanın. Bir Elido uç nokta gizli anahtarını döndürdükten sonra her teslimat yedi gün boyunca X-Elido-Signature üstbilgisinde yeni anahtarı, X-Elido-Signature-Previous üstbilgisinde eskisini taşır. İkisinden birini kabul edin, yeni gizli anahtarı dağıtın; eskisi kendiliğinden sona erer.
Elido'yu deneyin
Bir URL yapıştırın, çalışan bir kısa bağlantı alın
Kayıt gerekmez. Bağlantı 30 gün boyunca yaşar. Sonsuza kadar saklamak için kaydolun.
Kayıt gerekmez. Ücretsiz kaydolun, ayda 50 bağlantı oluşturun.