package webhook import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "io/ioutil" "net/http" "strings" ) type Hook struct { Signature string Payload []byte } const signaturePrefix = "" const signatureLength = len(signaturePrefix) + 64 func signBody(secret, body []byte) []byte { computed := hmac.New(sha256.New, secret) computed.Write(body) return []byte(computed.Sum(nil)) } func (h *Hook) SignedBy(secret []byte) bool { if len(h.Signature) != signatureLength || !strings.HasPrefix(h.Signature, signaturePrefix) { return false } actual := make([]byte, 20) hex.Decode(actual, []byte(h.Signature[5:])) return hmac.Equal(signBody(secret, h.Payload), actual) } func (h *Hook) Extract(dst interface{}) error { return json.Unmarshal(h.Payload, dst) } func HookFrom(req *http.Request) (hook *Hook, err error) { hook = new(Hook) if !strings.EqualFold(req.Method, "POST") { return nil, errors.New("Unknown method!") } if hook.Signature = req.Header.Get("X-Signature-SHA256"); len(hook.Signature) == 0 { return nil, errors.New("No signature!") } hook.Payload, err = ioutil.ReadAll(req.Body) return } func Verify(secret []byte, req *http.Request) (hook *Hook, err error) { hook, err = HookFrom(req) //Compare HMACs if err == nil && !hook.SignedBy(secret) { err = errors.New("Invalid signature") } return }