package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
)
var (
secret = []byte(os.Getenv("SF_WEBHOOK_SECRET"))
seen = sync.Map{}
)
func handle(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
if !verify(r.Header.Get("X-SF-Signature"), body) {
w.WriteHeader(401); return
}
deliveryID := r.Header.Get("X-SF-Delivery-Id")
if _, dupe := seen.LoadOrStore(deliveryID, true); dupe {
fmt.Fprintln(w, "ok (dedupe)"); return
}
fmt.Fprintln(w, "ok")
}
func verify(sigHeader string, body []byte) bool {
parts := map[string]string{}
for _, p := range strings.Split(sigHeader, ",") {
kv := strings.SplitN(p, "=", 2)
if len(kv) == 2 { parts[kv[0]] = kv[1] }
}
t, v1 := parts["t"], parts["v1"]
if t == "" || v1 == "" { return false }
ts, _ := strconv.ParseInt(t, 10, 64)
if abs(time.Now().Unix()-ts) > 300 { return false }
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(fmt.Sprintf("%s.%s", t, string(body))))
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(v1))
}
func abs(n int64) int64 { if n < 0 { return -n }; return n }