Verify webhooks in Go
⚠️
Preview — this feature is being built; the design below is the spec.
Check the syntic-signature header before trusting a webhook delivery. The header carries a timestamp t and one or more v1 signatures (several during secret rotation). The signature is the hex HMAC-SHA256 of t + "." + raw body, keyed with your endpoint’s signing secret. Reject the delivery if no v1 matches in constant time, or if t is more than 300 seconds away from your clock. Always hash the raw body bytes, before any JSON parsing.
package webhooks
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"math"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func Verify(header string, body []byte, secret string, now time.Time) bool {
var t string
var sigs []string
for _, part := range strings.Split(header, ",") {
k, v, ok := strings.Cut(strings.TrimSpace(part), "=")
if !ok {
continue
}
switch k {
case "t":
t = v
case "v1":
sigs = append(sigs, v)
}
}
ts, err := strconv.ParseInt(t, 10, 64)
if err != nil || math.Abs(float64(now.Unix()-ts)) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(t + "."))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
for _, s := range sigs {
if hmac.Equal([]byte(s), []byte(expected)) {
return true
}
}
return false
}
func Handler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil || !Verify(r.Header.Get("syntic-signature"), body, os.Getenv("SYNTIC_WEBHOOK_SECRET"), time.Now()) {
http.Error(w, "bad signature", http.StatusBadRequest)
return
}
enqueue(r.Header.Get("syntic-event-id"), body) // de-duplicate on the event id, process later
w.WriteHeader(http.StatusOK)
}Test it
Send a test event to your endpoint, or recompute a signature by hand as shown on Webhooks.
Was this page helpful?