Build a webhook receiver in Go
A small net/http handler that reads the raw body once, verifies the HMAC with a constant-time compare, and acknowledges before doing the work.
Go’s standard library has everything a webhook receiver needs — net/http for the
endpoint and crypto/hmac for verification. No framework required.
A minimal receiver
Read the body once with io.ReadAll, verify the HMAC with a constant-time hmac.Equal, return 200, and process in a
goroutine. Reject a bad signature with 401.
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"os"
)
var secret = []byte(os.Getenv("WEBHOOK_SECRET"))
func webhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body) // read the RAW body once
if err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
if !verify(body, r.Header.Get("X-Webhook-Signature")) {
http.Error(w, "bad signature", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK) // acknowledge fast
go process(body) // then process asynchronously
}
func verify(body []byte, sig string) bool {
mac := hmac.New(sha256.New, secret)
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
// hmac.Equal is constant-time.
return hmac.Equal([]byte(expected), []byte(sig))
} Acknowledge fast, process async
w.WriteHeader(http.StatusOK) returns immediately; the go process(body) goroutine does the slow work. For durability, enqueue the job instead of relying on an in-process
goroutine that a restart would drop.
Register it with http.HandleFunc("/webhooks", webhook), then point the provider at
a HookWatch endpoint. Every delivery is captured, so a failure is something you can inspect and replay rather than reproduce by hand.
Keep reading
Start debugging your webhooks.
Point one endpoint at HookWatch, capture a failure, and replay it once it’s fixed. Free during beta.