Repository: adnanh/webhook
Pull request #462 (2020-09-26 00:53:14)

Add Request object to hook package to simplify API
To avoid having to pass around so many parameters to the hook package,
create a Request object to store all request-specific data.  Update APIs
accordingly.


Patch:
diff --git a/internal/hook/hook.go b/internal/hook/hook.go
index 2c2d787..e6a9272 100644
--- a/internal/hook/hook.go
+++ b/internal/hook/hook.go
@@ -17,6 +17,7 @@ import (
 	"log"
 	"math"
 	"net"
+	"net/http"
 	"net/textproto"
 	"os"
 	"reflect"
@@ -47,6 +48,30 @@ const (
 	EnvNamespace string = "HOOK_"
 )
 
+// Request represents a webhook request.
+type Request struct {
+	// The request ID set by the RequestID middleware.
+	ID string
+
+	// The Content-Type of the request.
+	ContentType string
+
+	// The raw request body.
+	Body []byte
+
+	// Headers is a map of the parsed headers.
+	Headers map[string]interface{}
+
+	// Query is a map of the parsed URL query values.
+	Query map[string]interface{}
+
+	// Payload is a map of the parsed payload.
+	Payload map[string]interface{}
+
+	// The underlying HTTP request.
+	RawRequest *http.Request
+}
+
 // ParameterNodeError describes an error walking a parameter node.
 type ParameterNodeError struct {
 	key string
@@ -222,22 +247,26 @@ func CheckPayloadSignature512(payload []byte, secret, signature string) (string,
 	return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
 }
 
-func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
+func CheckScalrSignature(r *Request, signingKey string, checkDate bool) (bool, error) {
+	if r.Headers == nil {
+		return false, nil
+	}
+
 	// Check for the signature and date headers
-	if _, ok := headers["X-Signature"]; !ok {
+	if _, ok := r.Headers["X-Signature"]; !ok {
 		return false, nil
 	}
-	if _, ok := headers["Date"]; !ok {
+	if _, ok := r.Headers["Date"]; !ok {
 		return false, nil
 	}
 	if signingKey == "" {
 		return false, errors.New("signature validation signing key can not be empty")
 	}
 
-	providedSignature := headers["X-Signature"].(string)
-	dateHeader := headers["Date"].(string)
+	providedSignature := r.Headers["X-Signature"].(string)
+	dateHeader := r.Headers["Date"].(string)
 	mac := hmac.New(sha1.New, []byte(signingKey))
-	mac.Write(body)
+	mac.Write(r.Body)
 	mac.Write([]byte(dateHeader))
 	expectedSignature := hex.EncodeToString(mac.Sum(nil))
 
@@ -426,41 +455,41 @@ type Argument struct {
 
 // Get Argument method returns the value for the Argument's key name
 // based on the Argument's source
-func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string, error) {
+func (ha *Argument) Get(r *Request) (string, error) {
 	var source *map[string]interface{}
 	key := ha.Name
 
 	switch ha.Source {
 	case SourceHeader:
-		source = headers
+		source = &r.Headers
 		key = textproto.CanonicalMIMEHeaderKey(ha.Name)
 	case SourceQuery, SourceQueryAlias:
-		source = query
+		source = &r.Query
 	case SourcePayload:
-		source = payload
+		source = &r.Payload
 	case SourceString:
 		return ha.Name, nil
 	case SourceEntirePayload:
-		r, err := json.Marshal(payload)
+		res, err := json.Marshal(&r.Payload)
 		if err != nil {
 			return "", err
 		}
 
-		return string(r), nil
+		return string(res), nil
 	case SourceEntireHeaders:
-		r, err := json.Marshal(headers)
+		res, err := json.Marshal(&r.Headers)
 		if err != nil {
 			return "", err
 		}
 
-		return string(r), nil
+		return string(res), nil
 	case SourceEntireQuery:
-		r, err := json.Marshal(query)
+		res, err := json.Marshal(&r.Query)
 		if err != nil {
 			return "", err
 		}
 
-		return string(r), nil
+		return string(res), nil
 	}
 
 	if source != nil {
@@ -545,11 +574,11 @@ type Hook struct {
 
 // ParseJSONParameters decodes specified arguments to JSON objects and replaces the
 // string with the newly created object
-func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface{}) []error {
+func (h *Hook) ParseJSONParameters(r *Request) []error {
 	errors := make([]error, 0)
 
 	for i := range h.JSONStringParameters {
-		arg, err := h.JSONStringParameters[i].Get(headers, query, payload)
+		arg, err := h.JSONStringParameters[i].Get(r)
 		if err != nil {
 			errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
 		} else {
@@ -568,11 +597,11 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
 
 			switch h.JSONStringParameters[i].Source {
 			case SourceHeader:
-				source = headers
+				source = &r.Headers
 			case SourcePayload:
-				source = payload
+				source = &r.Payload
 			case SourceQuery, SourceQueryAlias:
-				source = query
+				source = &r.Query
 			}
 
 			if source != nil {
@@ -598,14 +627,14 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
 
 // ExtractCommandArguments creates a list of arguments, based on the
 // PassArgumentsToCommand property that is ready to be used with exec.Command()
-func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}) ([]string, []error) {
+func (h *Hook) ExtractCommandArguments(r *Request) ([]string, []error) {
 	args := make([]string, 0)
 	errors := make([]error, 0)
 
 	args = append(args, h.ExecuteCommand)
 
 	for i := range h.PassArgumentsToCommand {
-		arg, err := h.PassArgumentsToCommand[i].Get(headers, query, payload)
+		arg, err := h.PassArgumentsToCommand[i].Get(r)
 		if err != nil {
 			args = append(args, "")
 			errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
@@ -625,11 +654,11 @@ func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]inter
 // ExtractCommandArgumentsForEnv creates a list of arguments in key=value
 // format, based on the PassEnvironmentToCommand property that is ready to be used
 // with exec.Command().
-func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string]interface{}) ([]string, []error) {
+func (h *Hook) ExtractCommandArgumentsForEnv(r *Request) ([]string, []error) {
 	args := make([]string, 0)
 	errors := make([]error, 0)
 	for i := range h.PassEnvironmentToCommand {
-		arg, err := h.PassEnvironmentToCommand[i].Get(headers, query, payload)
+		arg, err := h.PassEnvironmentToCommand[i].Get(r)
 		if err != nil {
 			errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
 			continue
@@ -661,11 +690,11 @@ type FileParameter struct {
 // ExtractCommandArgumentsForFile creates a list of arguments in key=value
 // format, based on the PassFileToCommand property that is ready to be used
 // with exec.Command().
-func (h *Hook) ExtractCommandArgumentsForFile(headers, query, payload *map[string]interface{}) ([]FileParameter, []error) {
+func (h *Hook) ExtractCommandArgumentsForFile(r *Request) ([]FileParameter, []error) {
 	args := make([]FileParameter, 0)
 	errors := make([]error, 0)
 	for i := range h.PassFileToCommand {
-		arg, err := h.PassFileToCommand[i].Get(headers, query, payload)
+		arg, err := h.PassFileToCommand[i].Get(r)
 		if err != nil {
 			errors = append(errors, &ArgumentError{h.PassFileToCommand[i]})
 			continue
@@ -772,16 +801,16 @@ type Rules struct {
 
 // Evaluate finds the first rule property that is not nil and returns the value
 // it evaluates to
-func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
+func (r Rules) Evaluate(req *Request) (bool, error) {
 	switch {
 	case r.And != nil:
-		return r.And.Evaluate(headers, query, payload, body, remoteAddr)
+		return r.And.Evaluate(req)
 	case r.Or != nil:
-		return r.Or.Evaluate(headers, query, payload, body, remoteAddr)
+		return r.Or.Evaluate(req)
 	case r.Not != nil:
-		return r.Not.Evaluate(headers, query, payload, body, remoteAddr)
+		return r.Not.Evaluate(req)
 	case r.Match != nil:
-		return r.Match.Evaluate(headers, query, payload, body, remoteAddr)
+		return r.Match.Evaluate(req)
 	}
 
 	return false, nil
@@ -791,11 +820,11 @@ func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[
 type AndRule []Rules
 
 // Evaluate AndRule will return true if and only if all of ChildRules evaluate to true
-func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
+func (r AndRule) Evaluate(req *Request) (bool, error) {
 	res := true
 
 	for _, v := range r {
-		rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
+		rv, err := v.Evaluate(req)
 		if err != nil {
 			return false, err
 		}
@@ -813,11 +842,11 @@ func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body
 type OrRule []Rules
 
 // Evaluate OrRule will return true if any of ChildRules evaluate to true
-func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
+func (r OrRule) Evaluate(req *Request) (bool, error) {
 	res := false
 
 	for _, v := range r {
-		rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
+		rv, err := v.Evaluate(req)
 		if err != nil {
 			return false, err
 		}
@@ -835,8 +864,8 @@ func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *
 type NotRule Rules
 
 // Evaluate NotRule will return true if and only if ChildRule evaluates to false
-func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
-	rv, err := Rules(r).Evaluate(headers, query, payload, body, remoteAddr)
+func (r NotRule) Evaluate(req *Request) (bool, error) {
+	rv, err := Rules(r).Evaluate(req)
 	return !rv, err
 }
 
@@ -862,15 +891,15 @@ const (
 )
 
 // Evaluate MatchRule will return based on the type
-func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
+func (r MatchRule) Evaluate(req *Request) (bool, error) {
 	if r.Type == IPWhitelist {
-		return CheckIPWhitelist(remoteAddr, r.IPRange)
+		return CheckIPWhitelist(req.RawRequest.RemoteAddr, r.IPRange)
 	}
 	if r.Type == ScalrSignature {
-		return CheckScalrSignature(*headers, *body, r.Secret, true)
+		return CheckScalrSignature(req, r.Secret, true)
 	}
 
-	arg, err := r.Parameter.Get(headers, query, payload)
+	arg, err := r.Parameter.Get(req)
 	if err == nil {
 		switch r.Type {
 		case MatchValue:
@@ -878,13 +907,13 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
 		case MatchRegex:
 			return regexp.MatchString(r.Regex, arg)
 		case MatchHashSHA1:
-			_, err := CheckPayloadSignature(*body, r.Secret, arg)
+			_, err := CheckPayloadSignature(req.Body, r.Secret, arg)
 			return err == nil, err
 		case MatchHashSHA256:
-			_, err := CheckPayloadSignature256(*body, r.Secret, arg)
+			_, err := CheckPayloadSignature256(req.Body, r.Secret, arg)
 			return err == nil, err
 		case MatchHashSHA512:
-			_, err := CheckPayloadSignature512(*body, r.Secret, arg)
+			_, err := CheckPayloadSignature512(req.Body, r.Secret, arg)
 			return err == nil, err
 		}
 	}
diff --git a/webhook.go b/webhook.go
index 26872ed..f220d4c 100644
--- a/webhook.go
+++ b/webhook.go
@@ -302,10 +302,14 @@ func main() {
 }
 
 func hookHandler(w http.ResponseWriter, r *http.Request) {
-	rid := middleware.GetReqID(r.Context())
+	req := &hook.Request{
+		ID:         middleware.GetReqID(r.Context()),
+		RawRequest: r,
+	}
 
-	log.Printf("[%s] incoming HTTP %s request from %s\n", rid, r.Method, r.RemoteAddr)
+	log.Printf("[%s] incoming HTTP %s request from %s\n", req.ID, r.Method, r.RemoteAddr)
 
+	// TODO: rename this to avoid confusion with Request.ID
 	id := mux.Vars(r)["id"]
 
 	matchedHook := matchLoadedHook(id)
@@ -341,57 +345,54 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
 
 	if !allowedMethod {
 		w.WriteHeader(http.StatusMethodNotAllowed)
-		log.Printf("[%s] HTTP %s method not allowed for hook %q", rid, r.Method, id)
+		log.Printf("[%s] HTTP %s method not allowed for hook %q", req.ID, r.Method, id)
 
 		return
 	}
 
-	log.Printf("[%s] %s got matched\n", rid, id)
+	log.Printf("[%s] %s got matched\n", req.ID, id)
 
 	for _, responseHeader := range responseHeaders {
 		w.Header().Set(responseHeader.Name, responseHeader.Value)
 	}
 
-	var (
-		body []byte
-		err  error
-	)
+	var err error
 
 	// set contentType to IncomingPayloadContentType or header value
-	contentType := r.Header.Get("Content-Type")
+	req.ContentType = r.Header.Get("Content-Type")
 	if len(matchedHook.IncomingPayloadContentType) != 0 {
-		contentType = matchedHook.IncomingPayloadContentType
+		req.ContentType = matchedHook.IncomingPayloadContentType
 	}
 
-	isMultipart := strings.HasPrefix(contentType, "multipart/form-data;")
+	isMultipart := strings.HasPrefix(req.ContentType, "multipart/form-data;")
 
 	if !isMultipart {
-		body, err = ioutil.ReadAll(r.Body)
+		req.Body, err = ioutil.ReadAll(r.Body)
 		if err != nil {
-			log.Printf("[%s] error reading the request body: %+v\n", rid, err)
+			log.Printf("[%s] error reading the request body: %+v\n", req.ID, err)
 		}
 	}
 
 	// parse headers
-	headers := valuesToMap(r.Header)
+	req.Headers = valuesToMap(r.Header)
 
 	// parse query variables
-	query := valuesToMap(r.URL.Query())
+	req.Query = valuesToMap(r.URL.Query())
 
 	// parse body
-	var payload map[string]interface{}
+	// var payload map[string]interface{}
 
 	switch {
-	case strings.Contains(contentType, "json"):
-		decoder := json.NewDecoder(bytes.NewReader(body))
+	case strings.Contains(req.ContentType, "json"):
+		decoder := json.NewDecoder(bytes.NewReader(req.Body))
 		decoder.UseNumber()
 
 		var firstChar byte
-		for i := 0; i < len(body); i++ {
-			if unicode.IsSpace(rune(body[i])) {
+		for i := 0; i < len(req.Body); i++ {
+			if unicode.IsSpace(rune(req.Body[i])) {
 				continue
 			}
-			firstChar = body[i]
+			firstChar = req.Body[i]
 			break
 		}
 
@@ -399,36 +400,36 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
 			var arrayPayload interface{}
 			err := decoder.Decode(&arrayPayload)
 			if err != nil {
-				log.Printf("[%s] error parsing JSON array payload %+v\n", rid, err)
+				log.Printf("[%s] error parsing JSON array payload %+v\n", req.ID, err)
 			}
 
-			payload = make(map[string]interface{}, 1)
-			payload["root"] = arrayPayload
+			req.Payload = make(map[string]interface{}, 1)
+			req.Payload["root"] = arrayPayload
 		} else {
-			err := decoder.Decode(&payload)
+			err := decoder.Decode(&req.Payload)
 			if err != nil {
-				log.Printf("[%s] error parsing JSON payload %+v\n", rid, err)
+				log.Printf("[%s] error parsing JSON payload %+v\n", req.ID, err)
 			}
 		}
 
-	case strings.Contains(contentType, "x-www-form-urlencoded"):
-		fd, err := url.ParseQuery(string(body))
+	case strings.Contains(req.ContentType, "x-www-form-urlencoded"):
+		fd, err := url.ParseQuery(string(req.Body))
 		if err != nil {
-			log.Printf("[%s] error parsing form payload %+v\n", rid, err)
+			log.Printf("[%s] error parsing form payload %+v\n", req.ID, err)
 		} else {
-			payload = valuesToMap(fd)
+			req.Payload = valuesToMap(fd)
 		}
 
-	case strings.Contains(contentType, "xml"):
-		payload, err = mxj.NewMapXmlReader(bytes.NewReader(body))
+	case strings.Contains(req.ContentType, "xml"):
+		req.Payload, err = mxj.NewMapXmlReader(bytes.NewReader(req.Body))
 		if err != nil {
-			log.Printf("[%s] error parsing XML payload: %+v\n", rid, err)
+			log.Printf("[%s] error parsing XML payload: %+v\n", req.ID, err)
 		}
 
 	case isMultipart:
 		err = r.ParseMultipartForm(*maxMultipartMem)
 		if err != nil {
-			msg := fmt.Sprintf("[%s] error parsing multipart form: %+v\n", rid, err)
+			msg := fmt.Sprintf("[%s] error parsing multipart form: %+v\n", req.ID, err)
 			log.Println(msg)
 			w.WriteHeader(http.StatusInternalServerError)
 			fmt.Fprint(w, "Error occurred while parsing multipart form.")
@@ -436,14 +437,14 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
 		}
 
 		for k, v := range r.MultipartForm.Value {
-			log.Printf("[%s] found multipart form value %q", rid, k)
+			log.Printf("[%s] found multipart form value %q", req.ID, k)
 
-			if payload == nil {
-				payload = make(map[string]interface{})
+			if req.Payload == nil {
+				req.Payload = make(map[string]interface{})
 			}
 
 			// TODO(moorereason): support duplicate, named values
-			payload[k] = v[0]
+			req.Payload[k] = v[0]
 		}
 
 		for k, v := range r.MultipartForm.File {
@@ -472,11 +473,11 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
 			}
 
 			if parseAsJSON {
-				log.Printf("[%s] parsing multipart form file %q as JSON\n", rid, k)
+				log.Printf("[%s] parsing multipart form file %q as JSON\n", req.ID, k)
 
 				f, err := v[0].Open()
 				if err != nil {
-					msg := fmt.Sprintf("[%s] error parsing multipart form file: %+v\n", rid, err)
+					msg := fmt.Sprintf("[%s] error parsing multipart form file: %+v\n", req.ID, err)
 					log.Println(msg)
 					w.WriteHeader(http.StatusInternalServerError)
 					fmt.Fprint(w, "Error occurred while parsing multipart form file.")
@@ -489,24 +490,24 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
 				var part map[string]interface{}
 				err = decoder.Decode(&part)
 				if err != nil {
-					log.Printf("[%s] error parsing JSON payload file: %+v\n", rid, err)
+					log.Printf("[%s] error parsing JSON payload file: %+v\n", req.ID, err)
 				}
 
-				if payload == nil {
-					payload = make(map[string]interface{})
+				if req.Payload == nil {
+					req.Payload = make(map[string]interface{})
 				}
-				payload[k] = part
+				req.Payload[k] = part
 			}
 		}
 
 	default:
-		log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", rid, contentType)
+		log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", req.ID, req.ContentType)
 	}
 
 	// handle hook
-	errors := matchedHook.ParseJSONParameters(&headers, &query, &payload)
+	errors := matchedHook.ParseJSONParameters(req)
 	for _, err := range errors {
-		log.Printf("[%s] error parsing JSON parameters: %s\n", rid, err)
+		log.Printf("[%s] error parsing JSON parameters: %s\n", req.ID, err)
 	}
 
 	var ok bool
@@ -514,29 +515,29 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
 	if matchedHook.TriggerRule == nil {
 		ok = true
 	} else {
-		ok, err = matchedHook.TriggerRule.Evaluate(&headers, &query, &payload, &body, r.RemoteAddr)
+		ok, err = matchedHook.TriggerRule.Evaluate(req)
 		if err != nil {
 			if !hook.IsParameterNodeError(err) {
-				msg := fmt.Sprintf("[%s] error evaluating hook: %s", rid, err)
+				msg := fmt.Sprintf("[%s] error evaluating hook: %s", req.ID, err)
 				log.Println(msg)
 				w.WriteHeader(http.StatusInternalServerError)
 				fmt.Fprint(w, "Error occurred while evaluating hook rules.")
 				return
 			}
 
-			log.Printf("[%s] %v", rid, err)
+			log.Printf("[%s] %v", req.ID, err)
 		}
 	}
 
 	if ok {
-		log.Printf("[%s] %s hook triggered successfully\n", rid, matchedHook.ID)
+		log.Printf("[%s] %s hook triggered successfully\n", req.ID, matchedHook.ID)
 
 		for _, responseHeader := range matchedHook.ResponseHeaders {
 			w.Header().Set(responseHeader.Name, responseHeader.Value)
 		}
 
 		if matchedHook.CaptureCommandOutput {
-			response, err := handleHook(matchedHook, rid, &headers, &query, &payload, &body)
+			response, err := handleHook(matchedHook, req)
 
 			if err != nil {
 				w.WriteHeader(http.StatusInternalServerError)
@@ -549,16 +550,16 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
 			} else {
 				// Check if a success return code is configured for the hook
 				if matchedHook.SuccessHttpResponseCode != 0 {
-					writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
+					writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
 				}
 				fmt.Fprint(w, response)
 			}
 		} else {
-			go handleHook(matchedHook, rid, &headers, &query, &payload, &body)
+			go handleHook(matchedHook, req)
 
 			// Check if a success return code is configured for the hook
 			if matchedHook.SuccessHttpResponseCode != 0 {
-				writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
+				writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
 			}
 
 			fmt.Fprint(w, matchedHook.ResponseMessage)
@@ -568,16 +569,16 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
 
 	// Check if a return code is configured for the hook
 	if matchedHook.TriggerRuleMismatchHttpResponseCode != 0 {
-		writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode)
+		writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode)
 	}
 
 	// if none of the hooks got triggered
-	log.Printf("[%s] %s got matched, but didn't get triggered because the trigger rules were not satisfied\n", rid, matchedHook.ID)
+	log.Printf("[%s] %s got matched, but didn't get triggered because the trigger rules were not satisfied\n", req.ID, matchedHook.ID)
 
 	fmt.Fprint(w, "Hook rules were not satisfied.")
 }
 
-func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]interface{}, body *[]byte) (string, error) {
+func handleHook(h *hook.Hook, r *hook.Request) (string, error) {
 	var errors []error
 
 	// check the command exists
@@ -590,12 +591,12 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
 
 	cmdPath, err := exec.LookPath(lookpath)
 	if err != nil {
-		log.Printf("[%s] error in %s", rid, err)
+		log.Printf("[%s] error in %s", r.ID, err)
 
 		// check if parameters specified in execute-command by mistake
 		if strings.IndexByte(h.ExecuteCommand, ' ') != -1 {
 			s := strings.Fields(h.ExecuteCommand)[0]
-			log.Printf("[%s] use 'pass-arguments-to-command' to specify args for '%s'", rid, s)
+			log.Printf("[%s] use 'pass-arguments-to-command' to specify args for '%s'", r.ID, s)
 		}
 
 		return "", err
@@ -604,37 +605,37 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
 	cmd := exec.Command(cmdPath)
 	cmd.Dir = h.CommandWorkingDirectory
 
-	cmd.Args, errors = h.ExtractCommandArguments(headers, query, payload)
+	cmd.Args, errors = h.ExtractCommandArguments(r)
 	for _, err := range errors {
-		log.Printf("[%s] error extracting command arguments: %s\n", rid, err)
+		log.Printf("[%s] error extracting command arguments: %s\n", r.ID, err)
 	}
 
 	var envs []string
-	envs, errors = h.ExtractCommandArgumentsForEnv(headers, query, payload)
+	envs, errors = h.ExtractCommandArgumentsForEnv(r)
 
 	for _, err := range errors {
-		log.Printf("[%s] error extracting command arguments for environment: %s\n", rid, err)
+		log.Printf("[%s] error extracting command arguments for environment: %s\n", r.ID, err)
 	}
 
-	files, errors := h.ExtractCommandArgumentsForFile(headers, query, payload)
+	files, errors := h.ExtractCommandArgumentsForFile(r)
 
 	for _, err := range errors {
-		log.Printf("[%s] error extracting command arguments for file: %s\n", rid, err)
+		log.Printf("[%s] error extracting command arguments for file: %s\n", r.ID, err)
 	}
 
 	for i := range files {
 		tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
 		if err != nil {
-			log.Printf("[%s] error creating temp file [%s]", rid, err)
+			log.Printf("[%s] error creating temp file [%s]", r.ID, err)
 			continue
 		}
-		log.Printf("[%s] writing env %s file %s", rid, files[i].EnvName, tmpfile.Name())
+		log.Printf("[%s] writing env %s file %s", r.ID, files[i].EnvName, tmpfile.Name())
 		if _, err := tmpfile.Write(files[i].Data); err != nil {
-			log.Printf("[%s] error writing file %s [%s]", rid, tmpfile.Name(), err)
+			log.Printf("[%s] error writing file %s [%s]", r.ID, tmpfile.Name(), err)
 			continue
 		}
 		if err := tmpfile.Close(); err != nil {
-			log.Printf("[%s] error closing file %s [%s]", rid, tmpfile.Name(), err)
+			log.Printf("[%s] error closing file %s [%s]", r.ID, tmpfile.Name(), err)
 			continue
 		}
 
@@ -644,27 +645,27 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
 
 	cmd.Env = append(os.Environ(), envs...)
 
-	log.Printf("[%s] executing %s (%s) with arguments %q and environment %s using %s as cwd\n", rid, h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
+	log.Printf("[%s] executing %s (%s) with arguments %q and environment %s using %s as cwd\n", r.ID, h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
 
 	out, err := cmd.CombinedOutput()
 
-	log.Printf("[%s] command output: %s\n", rid, out)
+	log.Printf("[%s] command output: %s\n", r.ID, out)
 
 	if err != nil {
-		log.Printf("[%s] error occurred: %+v\n", rid, err)
+		log.Printf("[%s] error occurred: %+v\n", r.ID, err)
 	}
 
 	for i := range files {
 		if files[i].File != nil {
-			log.Printf("[%s] removing file %s\n", rid, files[i].File.Name())
+			log.Printf("[%s] removing file %s\n", r.ID, files[i].File.Name())
 			err := os.Remove(files[i].File.Name())
 			if err != nil {
-				log.Printf("[%s] error removing file %s [%s]", rid, files[i].File.Name(), err)
+				log.Printf("[%s] error removing file %s [%s]", r.ID, files[i].File.Name(), err)
 			}
 		}
 	}
 
-	log.Printf("[%s] finished handling %s\n", rid, h.ID)
+	log.Printf("[%s] finished handling %s\n", r.ID, h.ID)
 
 	return string(out), err
 }


Test patch:
diff --git a/internal/hook/hook_test.go b/internal/hook/hook_test.go
index 46bd444..c99edfb 100644
--- a/internal/hook/hook_test.go
+++ b/internal/hook/hook_test.go
@@ -1,6 +1,7 @@
 package hook
 
 import (
+	"net/http"
 	"os"
 	"reflect"
 	"strings"
@@ -136,7 +137,7 @@ func TestCheckPayloadSignature512(t *testing.T) {
 var checkScalrSignatureTests = []struct {
 	description       string
 	headers           map[string]interface{}
-	payload           []byte
+	body              []byte
 	secret            string
 	expectedSignature string
 	ok                bool
@@ -175,7 +176,11 @@ var checkScalrSignatureTests = []struct {
 
 func TestCheckScalrSignature(t *testing.T) {
 	for _, testCase := range checkScalrSignatureTests {
-		valid, err := CheckScalrSignature(testCase.headers, testCase.payload, testCase.secret, false)
+		r := &Request{
+			Headers: testCase.headers,
+			Body:    testCase.body,
+		}
+		valid, err := CheckScalrSignature(r, testCase.secret, false)
 		if valid != testCase.ok {
 			t.Errorf("failed to check scalr signature fot test case: %s\nexpected ok:%#v, got ok:%#v}",
 				testCase.description, testCase.ok, valid)
@@ -249,25 +254,30 @@ func TestExtractParameter(t *testing.T) {
 
 var argumentGetTests = []struct {
 	source, name            string
-	headers, query, payload *map[string]interface{}
+	headers, query, payload map[string]interface{}
 	value                   string
 	ok                      bool
 }{
-	{"header", "a", &map[string]interface{}{"A": "z"}, nil, nil, "z", true},
-	{"url", "a", nil, &map[string]interface{}{"a": "z"}, nil, "z", true},
-	{"payload", "a", nil, nil, &map[string]interface{}{"a": "z"}, "z", true},
-	{"string", "a", nil, nil, &map[string]interface{}{"a": "z"}, "a", true},
+	{"header", "a", map[string]interface{}{"A": "z"}, nil, nil, "z", true},
+	{"url", "a", nil, map[string]interface{}{"a": "z"}, nil, "z", true},
+	{"payload", "a", nil, nil, map[string]interface{}{"a": "z"}, "z", true},
+	{"string", "a", nil, nil, map[string]interface{}{"a": "z"}, "a", true},
 	// failures
-	{"header", "a", nil, &map[string]interface{}{"a": "z"}, &map[string]interface{}{"a": "z"}, "", false},  // nil headers
-	{"url", "a", &map[string]interface{}{"A": "z"}, nil, &map[string]interface{}{"a": "z"}, "", false},     // nil query
-	{"payload", "a", &map[string]interface{}{"A": "z"}, &map[string]interface{}{"a": "z"}, nil, "", false}, // nil payload
-	{"foo", "a", &map[string]interface{}{"A": "z"}, nil, nil, "", false},                                   // invalid source
+	{"header", "a", nil, map[string]interface{}{"a": "z"}, map[string]interface{}{"a": "z"}, "", false},  // nil headers
+	{"url", "a", map[string]interface{}{"A": "z"}, nil, map[string]interface{}{"a": "z"}, "", false},     // nil query
+	{"payload", "a", map[string]interface{}{"A": "z"}, map[string]interface{}{"a": "z"}, nil, "", false}, // nil payload
+	{"foo", "a", map[string]interface{}{"A": "z"}, nil, nil, "", false},                                  // invalid source
 }
 
 func TestArgumentGet(t *testing.T) {
 	for _, tt := range argumentGetTests {
 		a := Argument{tt.source, tt.name, "", false}
-		value, err := a.Get(tt.headers, tt.query, tt.payload)
+		r := &Request{
+			Headers: tt.headers,
+			Query:   tt.query,
+			Payload: tt.payload,
+		}
+		value, err := a.Get(r)
 		if (err == nil) != tt.ok || value != tt.value {
 			t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%v}", tt.source, tt.name, tt.value, tt.ok, value, err)
 		}
@@ -276,26 +286,31 @@ func TestArgumentGet(t *testing.T) {
 
 var hookParseJSONParametersTests = []struct {
 	params                     []Argument
-	headers, query, payload    *map[string]interface{}
-	rheaders, rquery, rpayload *map[string]interface{}
+	headers, query, payload    map[string]interface{}
+	rheaders, rquery, rpayload map[string]interface{}
 	ok                         bool
 }{
-	{[]Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true},
-	{[]Argument{Argument{"url", "a", "", false}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
-	{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
-	{[]Argument{Argument{"header", "z", "", false}}, &map[string]interface{}{"Z": `{}`}, nil, nil, &map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
+	{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true},
+	{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
+	{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
+	{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
 	// failures
-	{[]Argument{Argument{"header", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false},     // empty string
-	{[]Argument{Argument{"header", "y", "", false}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter
-	{[]Argument{Argument{"string", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false},     // invalid argument source
+	{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false},     // empty string
+	{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter
+	{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false},     // invalid argument source
 }
 
 func TestHookParseJSONParameters(t *testing.T) {
 	for _, tt := range hookParseJSONParametersTests {
 		h := &Hook{JSONStringParameters: tt.params}
-		err := h.ParseJSONParameters(tt.headers, tt.query, tt.payload)
+		r := &Request{
+			Headers: tt.headers,
+			Query:   tt.query,
+			Payload: tt.payload,
+		}
+		err := h.ParseJSONParameters(r)
 		if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
-			t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, *tt.rheaders, tt.ok, *tt.headers, (err == nil))
+			t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, tt.rheaders, tt.ok, tt.headers, (err == nil))
 		}
 	}
 }
@@ -303,19 +318,24 @@ func TestHookParseJSONParameters(t *testing.T) {
 var hookExtractCommandArgumentsTests = []struct {
 	exec                    string
 	args                    []Argument
-	headers, query, payload *map[string]interface{}
+	headers, query, payload map[string]interface{}
 	value                   []string
 	ok                      bool
 }{
-	{"test", []Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
+	{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
 	// failures
-	{"fail", []Argument{Argument{"payload", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
+	{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
 }
 
 func TestHookExtractCommandArguments(t *testing.T) {
 	for _, tt := range hookExtractCommandArgumentsTests {
 		h := &Hook{ExecuteCommand: tt.exec, PassArgumentsToCommand: tt.args}
-		value, err := h.ExtractCommandArguments(tt.headers, tt.query, tt.payload)
+		r := &Request{
+			Headers: tt.headers,
+			Query:   tt.query,
+			Payload: tt.payload,
+		}
+		value, err := h.ExtractCommandArguments(r)
 		if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
 			t.Errorf("failed to extract args {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
 		}
@@ -344,7 +364,7 @@ func TestHookExtractCommandArguments(t *testing.T) {
 var hookExtractCommandArgumentsForEnvTests = []struct {
 	exec                    string
 	args                    []Argument
-	headers, query, payload *map[string]interface{}
+	headers, query, payload map[string]interface{}
 	value                   []string
 	ok                      bool
 }{
@@ -352,14 +372,14 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
 	{
 		"test",
 		[]Argument{Argument{"header", "a", "", false}},
-		&map[string]interface{}{"A": "z"}, nil, nil,
+		map[string]interface{}{"A": "z"}, nil, nil,
 		[]string{"HOOK_a=z"},
 		true,
 	},
 	{
 		"test",
 		[]Argument{Argument{"header", "a", "MYKEY", false}},
-		&map[string]interface{}{"A": "z"}, nil, nil,
+		map[string]interface{}{"A": "z"}, nil, nil,
 		[]string{"MYKEY=z"},
 		true,
 	},
@@ -367,7 +387,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
 	{
 		"fail",
 		[]Argument{Argument{"payload", "a", "", false}},
-		&map[string]interface{}{"A": "z"}, nil, nil,
+		map[string]interface{}{"A": "z"}, nil, nil,
 		[]string{},
 		false,
 	},
@@ -376,7 +396,12 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
 func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
 	for _, tt := range hookExtractCommandArgumentsForEnvTests {
 		h := &Hook{ExecuteCommand: tt.exec, PassEnvironmentToCommand: tt.args}
-		value, err := h.ExtractCommandArgumentsForEnv(tt.headers, tt.query, tt.payload)
+		r := &Request{
+			Headers: tt.headers,
+			Query:   tt.query,
+			Payload: tt.payload,
+		}
+		value, err := h.ExtractCommandArgumentsForEnv(r)
 		if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
 			t.Errorf("failed to extract args for env {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
 		}
@@ -454,24 +479,24 @@ func TestHooksMatch(t *testing.T) {
 var matchRuleTests = []struct {
 	typ, regex, secret, value, ipRange string
 	param                              Argument
-	headers, query, payload            *map[string]interface{}
+	headers, query, payload            map[string]interface{}
 	body                               []byte
 	remoteAddr                         string
 	ok                                 bool
 	err                                bool
 }{
-	{"value", "", "", "z", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
-	{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
-	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
-	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
+	{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
+	{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
+	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
+	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
 	// failures
-	{"value", "", "", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
-	{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
-	{"value", "", "2", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
+	{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
+	{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
+	{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
 	// errors
-	{"regex", "*", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true},                   // invalid regex
-	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true},   // invalid hmac
-	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
+	{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true},                   // invalid regex
+	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true},   // invalid hmac
+	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
 	// IP whitelisting, valid cases
 	{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
 	{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
@@ -490,7 +515,16 @@ var matchRuleTests = []struct {
 func TestMatchRule(t *testing.T) {
 	for i, tt := range matchRuleTests {
 		r := MatchRule{tt.typ, tt.regex, tt.secret, tt.value, tt.param, tt.ipRange}
-		ok, err := r.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, tt.remoteAddr)
+		req := &Request{
+			Headers: tt.headers,
+			Query:   tt.query,
+			Payload: tt.payload,
+			Body:    tt.body,
+			RawRequest: &http.Request{
+				RemoteAddr: tt.remoteAddr,
+			},
+		}
+		ok, err := r.Evaluate(req)
 		if ok != tt.ok || (err != nil) != tt.err {
 			t.Errorf("%d failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", i, r, tt.ok, tt.err, ok, err)
 		}
@@ -500,7 +534,7 @@ func TestMatchRule(t *testing.T) {
 var andRuleTests = []struct {
 	desc                    string // description of the test case
 	rule                    AndRule
-	headers, query, payload *map[string]interface{}
+	headers, query, payload map[string]interface{}
 	body                    []byte
 	ok                      bool
 	err                     bool
@@ -511,7 +545,7 @@ var andRuleTests = []struct {
 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
 		},
-		&map[string]interface{}{"A": "z", "B": "y"}, nil, nil,
+		map[string]interface{}{"A": "z", "B": "y"}, nil, nil,
 		[]byte{},
 		true, false,
 	},
@@ -521,7 +555,7 @@ var andRuleTests = []struct {
 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
 		},
-		&map[string]interface{}{"A": "z", "B": "Y"}, nil, nil,
+		map[string]interface{}{"A": "z", "B": "Y"}, nil, nil,
 		[]byte{},
 		false, false,
 	},
@@ -548,7 +582,7 @@ var andRuleTests = []struct {
 				},
 			},
 		},
-		&map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil,
+		map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil,
 		[]byte{},
 		true, false,
 	},
@@ -557,14 +591,20 @@ var andRuleTests = []struct {
 	{
 		"invalid rule",
 		AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
-		&map[string]interface{}{"Y": "z"}, nil, nil, nil,
+		map[string]interface{}{"Y": "z"}, nil, nil, nil,
 		false, true,
 	},
 }
 
 func TestAndRule(t *testing.T) {
 	for _, tt := range andRuleTests {
-		ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
+		r := &Request{
+			Headers: tt.headers,
+			Query:   tt.query,
+			Payload: tt.payload,
+			Body:    tt.body,
+		}
+		ok, err := tt.rule.Evaluate(r)
 		if ok != tt.ok || (err != nil) != tt.err {
 			t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.desc, tt.ok, tt.err, ok, err)
 		}
@@ -574,7 +614,7 @@ func TestAndRule(t *testing.T) {
 var orRuleTests = []struct {
 	desc                    string // description of the test case
 	rule                    OrRule
-	headers, query, payload *map[string]interface{}
+	headers, query, payload map[string]interface{}
 	body                    []byte
 	ok                      bool
 	err                     bool
@@ -585,7 +625,7 @@ var orRuleTests = []struct {
 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
 		},
-		&map[string]interface{}{"A": "z", "B": "X"}, nil, nil,
+		map[string]interface{}{"A": "z", "B": "X"}, nil, nil,
 		[]byte{},
 		true, false,
 	},
@@ -595,7 +635,7 @@ var orRuleTests = []struct {
 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
 		},
-		&map[string]interface{}{"A": "X", "B": "y"}, nil, nil,
+		map[string]interface{}{"A": "X", "B": "y"}, nil, nil,
 		[]byte{},
 		true, false,
 	},
@@ -605,7 +645,7 @@ var orRuleTests = []struct {
 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
 		},
-		&map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil,
+		map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil,
 		[]byte{},
 		false, false,
 	},
@@ -615,7 +655,7 @@ var orRuleTests = []struct {
 		OrRule{
 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
 		},
-		&map[string]interface{}{"Y": "Z"}, nil, nil,
+		map[string]interface{}{"Y": "Z"}, nil, nil,
 		[]byte{},
 		false, true,
 	},
@@ -623,7 +663,13 @@ var orRuleTests = []struct {
 
 func TestOrRule(t *testing.T) {
 	for _, tt := range orRuleTests {
-		ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
+		r := &Request{
+			Headers: tt.headers,
+			Query:   tt.query,
+			Payload: tt.payload,
+			Body:    tt.body,
+		}
+		ok, err := tt.rule.Evaluate(r)
 		if ok != tt.ok || (err != nil) != tt.err {
 			t.Errorf("%#v:\nexpected ok: %#v, err: %v\ngot ok: %#v err: %v", tt.desc, tt.ok, tt.err, ok, err)
 		}
@@ -633,18 +679,24 @@ func TestOrRule(t *testing.T) {
 var notRuleTests = []struct {
 	desc                    string // description of the test case
 	rule                    NotRule
-	headers, query, payload *map[string]interface{}
+	headers, query, payload map[string]interface{}
 	body                    []byte
 	ok                      bool
 	err                     bool
 }{
-	{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
-	{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false},
+	{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
+	{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false},
 }
 
 func TestNotRule(t *testing.T) {
 	for _, tt := range notRuleTests {
-		ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
+		r := &Request{
+			Headers: tt.headers,
+			Query:   tt.query,
+			Payload: tt.payload,
+			Body:    tt.body,
+		}
+		ok, err := tt.rule.Evaluate(r)
 		if ok != tt.ok || (err != nil) != tt.err {
 			t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.rule, tt.ok, tt.err, ok, err)
 		}
diff --git a/webhook_test.go b/webhook_test.go
index 353d22d..c8a46cc 100644
--- a/webhook_test.go
+++ b/webhook_test.go
@@ -53,7 +53,11 @@ func TestStaticParams(t *testing.T) {
 	b := &bytes.Buffer{}
 	log.SetOutput(b)
 
-	_, err = handleHook(spHook, "test", &spHeaders, &map[string]interface{}{}, &map[string]interface{}{}, &[]byte{})
+	r := &hook.Request{
+		ID:      "test",
+		Headers: spHeaders,
+	}
+	_, err = handleHook(spHook, r)
 	if err != nil {
 		t.Fatalf("Unexpected error: %v\n", err)
 	}