Sari la conținut

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.

View as Markdown

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.

the platform signs each webhook payload using HMAC-SHA256 cu secretul de semnătură din abonamentul dvs. webhook. Semnătura este trimisă în X-Probo-Webhook-Signature header.

Mesajul semnat este concatenarea timestamp-ului și a corpului de cerere brut, separat de un colon:

{timestamp}:{body}

Where:

  • timestamp Este valoarea de la X-Probo-Webhook-Timestamp header (Unix seconds)
  • body este corpul de solicitare JSON

Use the full signing secret string (including the whsec_ Prefix) ca cheie HMAC. Nu ștergeți prefixul sau hexedecodarea secretului.

  1. Extract the headers

    Read X-Probo-Webhook-Timestamp and X-Probo-Webhook-Signature din cererea de.

  2. Build the signed message

    Concatenate the timestamp, a colon (:), și corpul de cerere brută.

  3. Compute the expected signature

    Calculate HMAC-SHA256 folosind secretul complet de semnătură (inclusiv whsec_ Prefix) ca cheie şi mesajul semnat ca intrare.

  4. Compare signatures

    Utilizaţi o comparaţie constantă a timpului pentru a verifica dacă semnătura calculată corespunde X-Probo-Webhook-Signature header.

  5. 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.

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
}
  • 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ția API necesită intrări de lungime egală.
  • Păstrați un secret separat pentru fiecare abonament și stocați-l într-un manager secret.
  • Return a generic 400 or 403 response. Do not reveal which verification check failed.
  • Record eventId Validarea timestamp limitează timpul de redare; idempotency previne efectele secundare duplicate.
SymptomLikely cause
Every signature failsCadrul parsează sau modifică corpul înainte de verificare
Only non-ASCII payloads failReceptorul a decodificat și re-codificat corpul în loc să hasheze byte brute
timingSafeEqual throwsSemnătura primită nu a fost validată mai întâi ca 32-byte hexadecimal
Livrările valabile sunt aproape ca staleCeasul receptorului nu este sincronizat sau timestamp-ul a fost tratat ca milisecunde
Verificarea funcționează cu o singură abonarePunctul final este selectarea secretului de abonament greșit

Ultima actualizare: