Signature Verification
Cum să verificați semnăturile platformei webhook cu HMAC-SHA256 peste corpul brut și timestamp, cu exemple de lucru în Go, Python și JavaScript.
Fiecare platformă webhook include o semnătură HMAC. Verificați-o înainte de a analiza corpul sau de a efectua efecte secundare. Verificarea semnăturilor dovedește că sarcina de utilizare și timestamp au fost produse cu secretul de semnătură al abonamentului; un timestamp verifică prospețimea limitează atacurile de replay.
How it works
Secțiune intitulată „How it works”the platform signs each webhook payload using HMAC-SHA256 cu secretul de semnătură din abonamentul dvs. webhook. Semnătura este trimisă în antetul X-Probo-Webhook-Signature.
Mesajul semnat este concatenarea timestamp-ului și a corpului de cerere brut, separat de un colon:
{timestamp}:{body}Where:
timestampeste valoarea din antetulX-Probo-Webhook-Timestamp(secunde Unix)bodyeste corpul de solicitare JSON brută
Use the full signing secret string (inclusiv prefixul whsec_) ca cheie HMAC.
Verification steps
Secțiune intitulată „Verification steps”-
Extract the headers
Citiți
X-Probo-Webhook-TimestampșiX-Probo-Webhook-Signaturedin cerere. -
Build the signed message
Conectați timestamp-ul, un colon (
:) și corpul de solicitare brută. -
Compute the expected signature
Calculați
HMAC-SHA256folosind secretul complet de semnătură (inclusiv prefixulwhsec_) ca cheie și mesajul semnat ca intrare. -
Compare signatures
Utilizați o comparație constantă a timpului pentru a verifica dacă semnătura calculată se potrivește cu antetul
X-Probo-Webhook-Signature. -
Check timestamp freshness
După ce semnătura se potrivește, respingeți solicitarea dacă timestamp-ul său este mai mare de 5 minute în trecut sau în viitor.
Examples
Secțiune intitulată „Examples”package main
import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "io" "net/http" "strconv" "time")
func verifyWebhook(r *http.Request, signingSecret string) ([]byte, error) { body, err := io.ReadAll(r.Body) if err != nil { return nil, err }
timestamp := r.Header.Get("X-Probo-Webhook-Timestamp") signature := r.Header.Get("X-Probo-Webhook-Signature") if timestamp == "" || signature == "" { return nil, fmt.Errorf("missing signature headers") }
mac := hmac.New(sha256.New, []byte(signingSecret)) mac.Write([]byte(timestamp)) mac.Write([]byte(":")) mac.Write(body)
received, err := hex.DecodeString(signature) if err != nil || !hmac.Equal(mac.Sum(nil), received) { return nil, fmt.Errorf("invalid signature") }
signedAt, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { return nil, fmt.Errorf("invalid timestamp") } delta := time.Now().Unix() - signedAt if delta > 300 || delta < -300 { return nil, fmt.Errorf("stale timestamp") }
return body, nil}import hashlibimport hmacimport reimport time
def verify_webhook( body: bytes, timestamp: str | None, signature: str | None, signing_secret: str,) -> bool: if ( timestamp is None or signature is None or re.fullmatch(r"[0-9]+", timestamp) is None ): return False
expected = hmac.new( signing_secret.encode(), timestamp.encode("ascii") + b":" + body, hashlib.sha256, ).digest()
if re.fullmatch(r"[0-9a-fA-F]{64}", signature) is None: return False if not hmac.compare_digest(expected, bytes.fromhex(signature)): return False
signed_at = int(timestamp) return abs(time.time() - signed_at) <= 300import { createHmac, timingSafeEqual } from "node:crypto";
function verifyWebhook(rawBody, timestamp, signature, signingSecret) { if ( !Buffer.isBuffer(rawBody) || typeof timestamp !== "string" || typeof signature !== "string" || !/^[0-9]+$/.test(timestamp) || !/^[0-9a-fA-F]{64}$/.test(signature) ) { return false; }
const expected = createHmac("sha256", signingSecret) .update(timestamp) .update(":") .update(rawBody) .digest(); const received = Buffer.from(signature, "hex");
if ( expected.length !== received.length || !timingSafeEqual(expected, received) ) { return false; }
const signedAt = Number(timestamp); return ( Number.isFinite(signedAt) && Math.abs(Date.now() / 1000 - signedAt) <= 300 );}Security recommendations
Secțiune intitulată „Security recommendations”- Verificați semnătura înainte de a analiza JSON, de a autoriza organizația sau de a coada lucrările.
- Refuzați timestamp-urile lipsă, deformate, învechite și datate în viitor. Exemplele utilizează o toleranță de 5 minute.
- Verificați mai întâi lungimea lor, unde comparațiaAPInecesită intrări de lungime egală.
- Păstrați un secret separat pentru fiecare abonament și stocați-l într-un manager secret.
- Returnați un răspuns generic
400sau403. Nu dezvăluiți care verificare a eșuat. - Înregistrează
eventIddupă verificare și o procesează o singură dată. validarea timestamp limitează timpul de redare; idempotency previne efectele secundare duplicate.
Troubleshooting
Secțiune intitulată „Troubleshooting”| Symptom | Likely cause |
|---|---|
| Every signature fails | Cadrul parsează sau modifică corpul înainte de verificare |
| Only non-ASCII payloads fail | Receptorul a decodificat și re-codificat corpul în loc să hasheze byte brute |
timingSafeEqual throws |
Semnătura primită nu a fost validată mai întâi ca 32-byte hexadecimal |
| Livrările valabile sunt aproape ca stale | Ceasul receptorului nu este sincronizat sau timestamp-ul a fost tratat ca milisecunde |
| Verificarea funcționează cu o singură abonare | Punctul final este selectarea secretului de abonament greșit |