Walidator podpisu webhooka
Twój dostawca wysyła nagłówek podpisu, Twój handler mówi, że się nie zgadza, a dokumentacja odpowiada SDK do zainstalowania. To nie mówi dlaczego. Wklej poniżej podpis, sekret i surowe body: przeliczamy HMAC w Twojej przeglądarce i przy niepowodzeniu sprawdzamy typowe przyczyny — zbędną spację w sekrecie, końcowy znak nowej linii, ponownie zserializowane body, przeterminowany timestamp.
W skrócie: podpis webhooka zawodzi prawie zawsze z jednego z pięciu powodów — zły sekret (test vs live), body ponownie zserializowane przez framework, timestamp poza oknem 5 minut, końcowy znak nowej linii lub pozostawiony prefiks sha256= / v0=. To narzędzie przelicza HMAC i mówi, który to.
Wklej niepasujący podpis
Cztery schematy podpisu
Wszystkie cztery to HMAC, ale różnią się tym, co jest hashowane i jak kodowany jest wynik. Jeden zły szczegół i każda dostawa zawodzi.
| Dostawca | Co jest podpisywane | Algorytm |
|---|---|---|
| Stripe | HMAC z t.body; podpis i timestamp w jednym nagłówku Stripe-Signature | SHA-256 hex |
| GitHub | HMAC surowego body; podpis w X-Hub-Signature-256 | SHA-256 hex |
| Shopify | HMAC surowego body; podpis w X-Shopify-Hmac-Sha256 | SHA-256 base64 |
| Slack | HMAC z v0:timestamp:body; potrzebny też nagłówek timestamp | SHA-256 hex |
Twilio jest wyjątkiem — hashuje kanoniczny URL plus posortowane parametry, nie body. Do tego użyj walidatora podpisu Twilio.
Pięć prawdziwych przyczyn
Uszeregowane według częstości w wątkach supportu.
| # | Przyczyna | Objaw | Rozwiązanie |
|---|---|---|---|
| 1 | Zły sekret | Każda dostawa zawodzi, nawet świeża | Pomyłka test vs live — dostawcy wydają inny sekret podpisu na tryb i na endpoint. |
| 2 | Ponownie zserializowane body | Zawodzi po sparsowaniu JSON przez framework | Podpisuj surowe bajty, nie JSON.stringify(parsed): odstępy i kolejność kluczy zmieniają hash. |
| 3 | Przeterminowany timestamp | Działa w testach, zawodzi przy ponownie wysłanych zdarzeniach | Stripe i Slack odrzucają starsze niż 5 minut. Nie wysyłaj ponownie zdarzenia sprzed godzin. |
| 4 | Końcowy znak nowej linii | Body przesunięte o bajt, podpis nigdy dobry | Proxy lub edytor dodał znak nowej linii. Narzędzie wykrywa dokładnie ten przypadek. |
| 5 | Prefiks porównany dosłownie | sha256= lub v0= wliczone do porównania | Porównuj tylko część hex/base64, nie prefiks schematu. |
Pułapka surowego body
Przyczyna numer jeden: weryfikujesz wobec body sparsowanego-i-ponownie-zserializowanego zamiast dokładnych bajtów. JSON.stringify(JSON.parse(body)) zmienia odstępy i kolejność kluczy, więc HMAC już nie pasuje. Odczytaj surowe body, zanim cokolwiek je sparsuje.
// Express: keep the RAW bytes, do NOT let JSON re-serialise them
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
const raw = req.body; // Buffer, exactly as received
verify(raw, req.headers['stripe-signature'], secret);
});# Flask: request.get_data() returns the raw body; request.json does NOT raw = request.get_data() # bytes, untouched verify(raw, request.headers['X-Hub-Signature-256'], secret)
// Next.js App Router: read the text before parsing
export async function POST(req) {
const raw = await req.text(); // sign THIS, not JSON.stringify(await req.json())
verify(raw, req.headers.get('stripe-signature'), secret);
}Zasada: przechwyć podpis i surowe bajty na wejściu, zweryfikuj, potem parsuj.
Timestampy i replay
Stripe i Slack wplatają timestamp w podpisywany ciąg i odrzucają wszystko starsze niż pięć minut — to ochrona przed replay, nie błąd. GitHub i Shopify nie, więc ważny podpis GitHub pozostaje ważny na zawsze. Jeśli narzędzie pokazuje ważny, ale wygasły, sekret i body są w porządku; po prostu wysyłasz ponownie stare zdarzenie. Wygeneruj świeżą dostawę zamiast wysyłać przechwyconą.
Gdy podpis się zgadza
Chcesz wysłać poprawnie podpisane zdarzenie testowe na własny endpoint? Signer webhooków tworzy je dla wszystkich sześciu dostawców. Aby przechwytywać i inspekcjonować prawdziwe dostawy, skieruj je na tunel Relay.
Najczęstsze pytania
Czy przechowujecie mój sekret podpisu?
Nie. HMAC liczony jest w Twojej przeglądarce przez Web Crypto; sekret i body nigdy nie trafiają na nasze serwery. Otwórz zakładkę Sieć: po kliknięciu Sprawdź nie wychodzi żadne żądanie.
Którzy dostawcy są obsługiwani?
Stripe, GitHub, Shopify i Slack — cztery schematy HMAC na body. Twilio podpisuje zamiast tego kanoniczny URL, więc ma własne narzędzie: walidator podpisu Twilio.
Tu przechodzi, ale mój serwer i tak odrzuca. Dlaczego?
Prawie zawsze body. Framework sparsował JSON, a Ty zserializowałeś je ponownie przed weryfikacją, co zmienia odstępy i kolejność kluczy. Podpisuj surowe bajty — zobacz snippety Express, Flask i Next powyżej.
Co znaczy „ważny, ale wygasły”?
HMAC jest poprawny, ale Stripe i Slack odrzucają też podpisy starsze niż pięć minut, by blokować replay. Jeśli przechwyciłeś zdarzenie i wysyłasz je później, weryfikacja zawodzi na timestampie, choć sekret jest dobry.