diff --git a/.schema/version.schema.json b/.schema/version.schema.json
index 0a3fad47d3..fa395b396d 100644
--- a/.schema/version.schema.json
+++ b/.schema/version.schema.json
@@ -1,244 +1,216 @@
 {
-    "$id": "https://github.com/ory/oathkeeper/.schema/version.schema.json",
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "oneOf": [
-        {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.40.3"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.40.3/.schema/config.schema.json"
-                }
-            ]
+  "$id": "https://github.com/ory/oathkeeper/.schema/version.schema.json",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "oneOf": [
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.40.3"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.40.2"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.40.2/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.40.3/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.40.2"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.40.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.40.1/spec/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.40.2/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.40.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.40.0"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.40.0/spec/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.40.1/spec/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.40.0"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.38.4-beta.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.4-beta.1/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.40.0/spec/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.38.4-beta.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.38.5-beta.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.5-beta.1/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.4-beta.1/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.38.5-beta.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.38.9-beta.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.9-beta.1/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.5-beta.1/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.38.9-beta.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.38.14-beta.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.14-beta.1/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.9-beta.1/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.38.14-beta.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.38.15-beta.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.15-beta.1/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.14-beta.1/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.38.15-beta.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.38.17-beta.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.17-beta.1/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.15-beta.1/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.38.17-beta.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.38.19-beta.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.19-beta.1/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.17-beta.1/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.38.19-beta.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "properties": {
-                        "version": {
-                            "const": "v0.38.20-beta.1"
-                        }
-                    },
-                    "required": [
-                        "version"
-                    ]
-                },
-                {
-                    "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.20-beta.1/.schema/config.schema.json"
-                }
-            ]
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.19-beta.1/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "properties": {
+            "version": {
+              "const": "v0.38.20-beta.1"
+            }
+          },
+          "required": ["version"]
         },
         {
-            "allOf": [
-                {
-                    "oneOf": [
-                        {
-                            "properties": {
-                                "version": {
-                                    "type": "string",
-                                    "maxLength": 0
-                                }
-                            },
-                            "required": [
-                                "version"
-                            ]
-                        },
-                        {
-                            "not": {
-                                "properties": {
-                                    "version": {}
-                                },
-                                "required": [
-                                    "version"
-                                ]
-                            }
-                        }
-                    ]
-                },
-                {
-                    "$ref": "#/oneOf/0/allOf/1"
+          "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.38.20-beta.1/.schema/config.schema.json"
+        }
+      ]
+    },
+    {
+      "allOf": [
+        {
+          "oneOf": [
+            {
+              "properties": {
+                "version": {
+                  "type": "string",
+                  "maxLength": 0
                 }
-            ]
+              },
+              "required": ["version"]
+            },
+            {
+              "not": {
+                "properties": {
+                  "version": {}
+                },
+                "required": ["version"]
+              }
+            }
+          ]
+        },
+        {
+          "$ref": "#/oneOf/0/allOf/1"
         }
-    ],
-    "title": "All Versions of the ORY Oathkeeper Configuration",
-    "type": "object"
-}
\ No newline at end of file
+      ]
+    }
+  ],
+  "title": "All Versions of the ORY Oathkeeper Configuration",
+  "type": "object"
+}
diff --git a/api/decision.go b/api/decision.go
index d1cfbb37dd..ed3230dbff 100644
--- a/api/decision.go
+++ b/api/decision.go
@@ -83,7 +83,7 @@ func (h *DecisionHandler) decisions(w http.ResponseWriter, r *http.Request) {
 		fields["subject"] = sess.Subject
 	}
 
-	rl, err := h.r.RuleMatcher().Match(r.Context(), r.Method, r.URL, rule.ProtocolHTTP)
+	rl, err := h.r.RuleMatcher().Match(r.Context(), r.Method, r.URL, r.Header, rule.ProtocolHTTP)
 	if err != nil {
 		h.r.Logger().WithError(err).
 			WithFields(fields).
diff --git a/api/decision_test.go b/api/decision_test.go
index d4c6d07236..c2828ab40b 100644
--- a/api/decision_test.go
+++ b/api/decision_test.go
@@ -352,7 +352,7 @@ func (*decisionHandlerRegistryMock) Logger() *logrusx.Logger {
 	return logrusx.New("", "")
 }
 
-func (m *decisionHandlerRegistryMock) Match(ctx context.Context, method string, u *url.URL, _ rule.Protocol) (*rule.Rule, error) {
+func (m *decisionHandlerRegistryMock) Match(ctx context.Context, method string, u *url.URL, _ http.Header, _ rule.Protocol) (*rule.Rule, error) {
 	args := m.Called(ctx, method, u)
 	return args.Get(0).(*rule.Rule), args.Error(1)
 }
diff --git a/middleware/grpc_middleware.go b/middleware/grpc_middleware.go
index 45fec6380d..0bd96cce8d 100644
--- a/middleware/grpc_middleware.go
+++ b/middleware/grpc_middleware.go
@@ -89,7 +89,7 @@ func (m *middleware) unaryInterceptor(ctx context.Context, req interface{}, info
 
 	log.Debug("matching HTTP request build from gRPC")
 
-	r, err := m.RuleMatcher().Match(traceCtx, httpReq.Method, httpReq.URL, rule.ProtocolGRPC)
+	r, err := m.RuleMatcher().Match(traceCtx, httpReq.Method, httpReq.URL, httpReq.Header, rule.ProtocolGRPC)
 	if err != nil {
 		log.WithError(err).Warn("could not find a matching rule")
 		span.SetAttributes(attribute.String("oathkeeper.verdict", "denied"))
@@ -138,7 +138,7 @@ func (m *middleware) streamInterceptor(
 
 	log.Debug("matching HTTP request build from gRPC")
 
-	r, err := m.RuleMatcher().Match(ctx, httpReq.Method, httpReq.URL, rule.ProtocolGRPC)
+	r, err := m.RuleMatcher().Match(ctx, httpReq.Method, httpReq.URL, httpReq.Header, rule.ProtocolGRPC)
 	if err != nil {
 		log.WithError(err).Warn("could not find a matching rule")
 		span.SetAttributes(attribute.String("oathkeeper.verdict", "denied"))
diff --git a/proxy/proxy.go b/proxy/proxy.go
index 3f9bea36bf..e85a397ab1 100644
--- a/proxy/proxy.go
+++ b/proxy/proxy.go
@@ -108,7 +108,7 @@ func (d *Proxy) RoundTrip(r *http.Request) (*http.Response, error) {
 
 func (d *Proxy) Rewrite(r *httputil.ProxyRequest) {
 	EnrichRequestedURL(r)
-	rl, err := d.r.RuleMatcher().Match(r.Out.Context(), r.Out.Method, r.Out.URL, rule.ProtocolHTTP)
+	rl, err := d.r.RuleMatcher().Match(r.Out.Context(), r.Out.Method, r.Out.URL, r.Out.Header, rule.ProtocolHTTP)
 	if err != nil {
 		*r.Out = *r.Out.WithContext(context.WithValue(r.Out.Context(), director, err))
 		return
diff --git a/rule/matcher.go b/rule/matcher.go
index 69d7b2e75e..6d37368bdb 100644
--- a/rule/matcher.go
+++ b/rule/matcher.go
@@ -5,6 +5,7 @@ package rule
 
 import (
 	"context"
+	"net/http"
 	"net/url"
 )
 
@@ -12,7 +13,7 @@ type (
 	Protocol int
 
 	Matcher interface {
-		Match(ctx context.Context, method string, u *url.URL, protocol Protocol) (*Rule, error)
+		Match(ctx context.Context, method string, u *url.URL, headers http.Header, protocol Protocol) (*Rule, error)
 	}
 )
 
diff --git a/rule/matcher_test.go b/rule/matcher_test.go
index 28ee2d051e..70a17486d9 100644
--- a/rule/matcher_test.go
+++ b/rule/matcher_test.go
@@ -6,6 +6,7 @@ package rule
 import (
 	"context"
 	"fmt"
+	"net/http"
 	"net/url"
 	"testing"
 
@@ -49,6 +50,15 @@ var testRules = []Rule{
 		Mutators:       []Handler{{Handler: "id_token"}},
 		Upstream:       Upstream{URL: "http://localhost:3333/", StripPath: "/foo", PreserveHost: false},
 	},
+	{
+		ID:             "foo4",
+		Match:          &Match{URL: "https://localhost:343/<baz|bar>", Methods: []string{"PATCH"}, Headers: http.Header{"Content-Type": {"application/some-app.v2+json"}}},
+		Description:    "Patch users rule for version 2",
+		Authorizer:     Handler{Handler: "deny"},
+		Authenticators: []Handler{{Handler: "oauth2_introspection"}},
+		Mutators:       []Handler{{Handler: "id_token"}},
+		Upstream:       Upstream{URL: "http://localhost:3333/", StripPath: "/foo", PreserveHost: false},
+	},
 	{
 		ID:             "grpc1",
 		Match:          &MatchGRPC{Authority: "<baz|bar>.example.com", FullMethod: "grpc.api/Call"},
@@ -88,6 +98,15 @@ var testRulesGlob = []Rule{
 		Mutators:       []Handler{{Handler: "id_token"}},
 		Upstream:       Upstream{URL: "http://localhost:3333/", StripPath: "/foo", PreserveHost: false},
 	},
+	{
+		ID:             "foo4",
+		Match:          &Match{URL: "https://localhost:343/<{baz*,bar*}>", Methods: []string{"PATCH"}, Headers: http.Header{"Content-Type": {"application/some-app.v2+json"}}},
+		Description:    "Patch users rule with version 2",
+		Authorizer:     Handler{Handler: "deny"},
+		Authenticators: []Handler{{Handler: "oauth2_introspection"}},
+		Mutators:       []Handler{{Handler: "id_token"}},
+		Upstream:       Upstream{URL: "http://localhost:3333/", StripPath: "/foo", PreserveHost: false},
+	},
 	{
 		ID:             "grpc1",
 		Match:          &MatchGRPC{Authority: "<{baz*,bar*}>.example.com", FullMethod: "grpc.api/Call"},
@@ -97,6 +116,15 @@ var testRulesGlob = []Rule{
 		Mutators:       []Handler{{Handler: "id_token", Config: []byte(`{"issuer":"anything"}`)}},
 		Upstream:       Upstream{URL: "http://bar.example.com/", PreserveHost: false},
 	},
+	{
+		ID:             "grpc2",
+		Match:          &MatchGRPC{Authority: "<{baz*,bar*}>.example.com", FullMethod: "grpc.api/CallWithHeader", Headers: http.Header{"Content-Type": {"application/some-app.v2+json"}}},
+		Description:    "gRPC Rule with version 2",
+		Authorizer:     Handler{Handler: "allow", Config: []byte(`{"type":"any"}`)},
+		Authenticators: []Handler{{Handler: "anonymous", Config: []byte(`{"name":"anonymous1"}`)}},
+		Mutators:       []Handler{{Handler: "id_token", Config: []byte(`{"issuer":"anything"}`)}},
+		Upstream:       Upstream{URL: "http://bar.example.com/", PreserveHost: false},
+	},
 }
 
 func TestMatcher(t *testing.T) {
@@ -105,8 +133,8 @@ func TestMatcher(t *testing.T) {
 		Repository
 	}
 
-	var testMatcher = func(t *testing.T, matcher Matcher, method string, url string, protocol Protocol, expectErr bool, expect *Rule) {
-		r, err := matcher.Match(context.Background(), method, mustParseURL(t, url), protocol)
+	var testMatcher = func(t *testing.T, matcher Matcher, method string, url string, headers http.Header, protocol Protocol, expectErr bool, expect *Rule) {
+		r, err := matcher.Match(context.Background(), method, mustParseURL(t, url), headers, protocol)
 		if expectErr {
 			require.Error(t, err)
 		} else {
@@ -121,24 +149,24 @@ func TestMatcher(t *testing.T) {
 	} {
 		t.Run(fmt.Sprintf("regexp matcher=%s", name), func(t *testing.T) {
 			t.Run("case=empty", func(t *testing.T) {
-				testMatcher(t, matcher, "GET", "https://localhost:34/baz", ProtocolHTTP, true, nil)
-				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
-				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "GET", "https://localhost:34/baz", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
 			})
 
 			require.NoError(t, matcher.Set(context.Background(), testRules))
 
 			t.Run("case=created", func(t *testing.T) {
-				testMatcher(t, matcher, "GET", "https://localhost:34/baz", ProtocolHTTP, false, &testRules[1])
-				testMatcher(t, matcher, "GET", "https://localhost:34/baz", ProtocolGRPC, true, nil)
-				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", ProtocolHTTP, false, &testRules[0])
-				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", ProtocolGRPC, true, nil)
-				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
-				testMatcher(t, matcher, "POST", "grpc://bar.example.com/grpc.api/Call", ProtocolGRPC, false, &testRules[3])
+				testMatcher(t, matcher, "GET", "https://localhost:34/baz", http.Header{}, ProtocolHTTP, false, &testRules[1])
+				testMatcher(t, matcher, "GET", "https://localhost:34/baz", http.Header{}, ProtocolGRPC, true, nil)
+				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, false, &testRules[0])
+				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", http.Header{}, ProtocolGRPC, true, nil)
+				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "POST", "grpc://bar.example.com/grpc.api/Call", http.Header{}, ProtocolGRPC, false, &testRules[4])
 			})
 
 			t.Run("case=cache", func(t *testing.T) {
-				r, err := matcher.Match(context.Background(), "GET", mustParseURL(t, "https://localhost:34/baz"), ProtocolHTTP)
+				r, err := matcher.Match(context.Background(), "GET", mustParseURL(t, "https://localhost:34/baz"), http.Header{}, ProtocolHTTP)
 				require.NoError(t, err)
 				got, err := matcher.Get(context.Background(), r.ID)
 				require.NoError(t, err)
@@ -146,38 +174,42 @@ func TestMatcher(t *testing.T) {
 			})
 
 			t.Run("case=nil url", func(t *testing.T) {
-				_, err := matcher.Match(context.Background(), "GET", nil, ProtocolHTTP)
+				_, err := matcher.Match(context.Background(), "GET", nil, http.Header{}, ProtocolHTTP)
 				require.Error(t, err)
 			})
 
 			require.NoError(t, matcher.Set(context.Background(), testRules[1:]))
 
 			t.Run("case=updated", func(t *testing.T) {
-				testMatcher(t, matcher, "GET", "https://localhost:34/baz", ProtocolHTTP, false, &testRules[1])
-				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
-				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "GET", "https://localhost:34/baz", http.Header{}, ProtocolHTTP, false, &testRules[1])
+				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "PATCH", "https://localhost:343/bar", http.Header{"Content-Type": {"application/some-app.v1+json"}}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "PATCH", "https://localhost:343/bar", http.Header{"Content-Type": {"application/some-app.v2+json"}}, ProtocolHTTP, false, &testRules[3])
 			})
 		})
 		t.Run(fmt.Sprintf("glob matcher=%s", name), func(t *testing.T) {
 			require.NoError(t, matcher.SetMatchingStrategy(context.Background(), configuration.Glob))
 			require.NoError(t, matcher.Set(context.Background(), []Rule{}))
 			t.Run("case=empty", func(t *testing.T) {
-				testMatcher(t, matcher, "GET", "https://localhost:34/baz", ProtocolHTTP, true, nil)
-				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
-				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "GET", "https://localhost:34/baz", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
 			})
 
 			require.NoError(t, matcher.Set(context.Background(), testRulesGlob))
 
 			t.Run("case=created", func(t *testing.T) {
-				testMatcher(t, matcher, "GET", "https://localhost:34/baz", ProtocolHTTP, false, &testRulesGlob[1])
-				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", ProtocolHTTP, false, &testRulesGlob[0])
-				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
-				testMatcher(t, matcher, "POST", "grpc://bar.example.com/grpc.api/Call", ProtocolGRPC, false, &testRulesGlob[3])
+				testMatcher(t, matcher, "GET", "https://localhost:34/baz", http.Header{}, ProtocolHTTP, false, &testRulesGlob[1])
+				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, false, &testRulesGlob[0])
+				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "POST", "grpc://bar.example.com/grpc.api/Call", http.Header{}, ProtocolGRPC, false, &testRulesGlob[4])
+				testMatcher(t, matcher, "POST", "grpc://bar.example.com/grpc.api/CallWithHeader", http.Header{"Content-Type": []string{"application/some-app.v1+json"}}, ProtocolGRPC, true, nil)
+				testMatcher(t, matcher, "POST", "grpc://bar.example.com/grpc.api/CallWithHeader", http.Header{"Content-Type": []string{"application/some-app.v2+json"}}, ProtocolGRPC, false, &testRulesGlob[5])
 			})
 
 			t.Run("case=cache", func(t *testing.T) {
-				r, err := matcher.Match(context.Background(), "GET", mustParseURL(t, "https://localhost:34/baz"), ProtocolHTTP)
+				r, err := matcher.Match(context.Background(), "GET", mustParseURL(t, "https://localhost:34/baz"), http.Header{}, ProtocolHTTP)
 				require.NoError(t, err)
 				got, err := matcher.Get(context.Background(), r.ID)
 				require.NoError(t, err)
@@ -187,9 +219,11 @@ func TestMatcher(t *testing.T) {
 			require.NoError(t, matcher.Set(context.Background(), testRulesGlob[1:]))
 
 			t.Run("case=updated", func(t *testing.T) {
-				testMatcher(t, matcher, "GET", "https://localhost:34/baz", ProtocolHTTP, false, &testRulesGlob[1])
-				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
-				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "GET", "https://localhost:34/baz", http.Header{}, ProtocolHTTP, false, &testRulesGlob[1])
+				testMatcher(t, matcher, "POST", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", http.Header{}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "PATCH", "https://localhost:343/bar", http.Header{"Content-Type": []string{"application/some-app.v1+json"}}, ProtocolHTTP, true, nil)
+				testMatcher(t, matcher, "PATCH", "https://localhost:343/bar", http.Header{"Content-Type": []string{"application/some-app.v2+json"}}, ProtocolHTTP, false, &testRulesGlob[3])
 			})
 		})
 	}
diff --git a/rule/repository_memory.go b/rule/repository_memory.go
index 3dc90c1b03..6c343f4c32 100644
--- a/rule/repository_memory.go
+++ b/rule/repository_memory.go
@@ -110,7 +110,7 @@ func (m *RepositoryMemory) Set(ctx context.Context, rules []Rule) error {
 	return nil
 }
 
-func (m *RepositoryMemory) Match(ctx context.Context, method string, u *url.URL, protocol Protocol) (*Rule, error) {
+func (m *RepositoryMemory) Match(ctx context.Context, method string, u *url.URL, headers http.Header, protocol Protocol) (*Rule, error) {
 	if u == nil {
 		return nil, errors.WithStack(errors.New("nil URL provided"))
 	}
@@ -121,7 +121,7 @@ func (m *RepositoryMemory) Match(ctx context.Context, method string, u *url.URL,
 	var rules []*Rule
 	for k := range m.rules {
 		r := &m.rules[k]
-		if matched, err := r.IsMatching(m.matchingStrategy, method, u, protocol); err != nil {
+		if matched, err := r.IsMatching(m.matchingStrategy, method, u, headers, protocol); err != nil {
 			return nil, errors.WithStack(err)
 		} else if matched {
 			rules = append(rules, r)
@@ -129,7 +129,7 @@ func (m *RepositoryMemory) Match(ctx context.Context, method string, u *url.URL,
 	}
 	for k := range m.invalidRules {
 		r := &m.invalidRules[k]
-		if matched, err := r.IsMatching(m.matchingStrategy, method, u, protocol); err != nil {
+		if matched, err := r.IsMatching(m.matchingStrategy, method, u, headers, protocol); err != nil {
 			return nil, errors.WithStack(err)
 		} else if matched {
 			rules = append(rules, r)
diff --git a/rule/rule.go b/rule/rule.go
index 6a4b851e66..dbe9819435 100644
--- a/rule/rule.go
+++ b/rule/rule.go
@@ -6,6 +6,7 @@ package rule
 import (
 	"encoding/json"
 	"fmt"
+	"net/http"
 	"net/url"
 	"strings"
 
@@ -19,7 +20,7 @@ type Match struct {
 	// An array of HTTP methods (e.g. GET, POST, PUT, DELETE, ...). When ORY Oathkeeper searches for rules
 	// to decide what to do with an incoming request to the proxy server, it compares the HTTP method of the incoming
 	// request with the HTTP methods of each rules. If a match is found, the rule is considered a partial match.
-	// If the matchesUrl field is satisfied as well, the rule is considered a full match.
+	// If the matchesUrl and matchesHeaders fields are satisfied as well, the rule is considered a full match.
 	Methods []string `json:"methods"`
 
 	// This field represents the URL pattern this rule matches. When ORY Oathkeeper searches for rules
@@ -34,22 +35,33 @@ type Match struct {
 	// The following regexp example matches all paths of the domain `mydomain.com`: `https://mydomain.com/<.*>`.
 	// The glob equivalent of the above regexp example is `https://mydomain.com/<*>`.
 	URL string `json:"url"`
+
+	// A map of HTTP headers. When ORY Oathkeeper searches for rules
+	// to decide what to do with an incoming request to the proxy server, it compares the HTTP headers of the incoming
+	// request with the HTTP headers of each rules. If a match is found, the rule is considered a partial match.
+	// For headers with values in array format (e.g. User-Agent headers), the rule header value must match at all
+	// of the request header values.
+	// If the matchesUrl and matchesMethods fields are satisfied as well, the rule is considered a full match.
+	Headers http.Header `json:"headers"`
 }
 
-func (m *Match) GetURL() string       { return m.URL }
-func (m *Match) GetMethods() []string { return m.Methods }
-func (m *Match) Protocol() Protocol   { return ProtocolHTTP }
+func (m *Match) GetURL() string          { return m.URL }
+func (m *Match) GetMethods() []string    { return m.Methods }
+func (m *Match) Protocol() Protocol      { return ProtocolHTTP }
+func (m *Match) GetHeaders() http.Header { return m.Headers }
 
 type MatchGRPC struct {
-	Authority  string `json:"authority"`
-	FullMethod string `json:"full_method"`
+	Authority  string      `json:"authority"`
+	FullMethod string      `json:"full_method"`
+	Headers    http.Header `json:"headers"`
 }
 
 func (m *MatchGRPC) GetURL() string {
 	return fmt.Sprintf("grpc://%s/%s", m.Authority, m.FullMethod)
 }
-func (m *MatchGRPC) GetMethods() []string { return []string{"POST"} }
-func (m *MatchGRPC) Protocol() Protocol   { return ProtocolGRPC }
+func (m *MatchGRPC) GetMethods() []string    { return []string{"POST"} }
+func (m *MatchGRPC) Protocol() Protocol      { return ProtocolGRPC }
+func (m *MatchGRPC) GetHeaders() http.Header { return m.Headers }
 
 type Handler struct {
 	// Handler identifies the implementation which will be used to handle this specific request. Please read the user
@@ -82,6 +94,7 @@ type URLProvider interface {
 	GetURL() string
 	GetMethods() []string
 	Protocol() Protocol
+	GetHeaders() http.Header
 }
 
 // Rule is a single rule that will get checked on every HTTP request.
@@ -202,22 +215,27 @@ func (r *Rule) GetID() string {
 	return r.ID
 }
 
-// IsMatching checks whether the provided url and method match the rule.
+// IsMatching checks whether the provided url, method and headers match the rule.
 // An error will be returned if a regexp matching strategy is selected and regexp timeout occurs.
-func (r *Rule) IsMatching(strategy configuration.MatchingStrategy, method string, u *url.URL, protocol Protocol) (bool, error) {
+func (r *Rule) IsMatching(strategy configuration.MatchingStrategy, method string, u *url.URL, headers http.Header, protocol Protocol) (bool, error) {
 	if r.Match == nil {
 		return false, errors.New("no Match configured (was nil)")
 	}
 	if !stringInSlice(method, r.Match.GetMethods()) {
 		return false, nil
 	}
-	if err := ensureMatchingEngine(r, strategy); err != nil {
-		return false, err
-	}
 	if r.Match.Protocol() != protocol {
 		return false, nil
 	}
 
+	if !matchHeaders(headers, r.Match.GetHeaders()) {
+		return false, nil
+	}
+
+	if err := ensureMatchingEngine(r, strategy); err != nil {
+		return false, err
+	}
+
 	matchAgainst := fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, u.Path)
 	return r.matchingEngine.IsMatching(r.Match.GetURL(), matchAgainst)
 }
@@ -257,6 +275,39 @@ func ensureMatchingEngine(rule *Rule, strategy configuration.MatchingStrategy) e
 	return errors.Wrap(ErrUnknownMatchingStrategy, string(strategy))
 }
 
+func matchHeaders(requestHeaders http.Header, matchHeaders http.Header) bool {
+	for matcherHeaderKey, matcherHeaderValues := range matchHeaders {
+		foundMatch := false
+		for requestHeaderKey, requestHeaderValues := range requestHeaders {
+			if strings.EqualFold(matcherHeaderKey, requestHeaderKey) {
+				if slicesEqualFold(requestHeaderValues, matcherHeaderValues) {
+					foundMatch = true
+					// Break if we find the matching values. Report match found
+					break
+				}
+				// Break if we find the matching key but value do not match
+				break
+			}
+		}
+		if !foundMatch {
+			return false
+		}
+	}
+	return true
+}
+
+func slicesEqualFold(a, b []string) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i, v := range a {
+		if !strings.EqualFold(v, b[i]) {
+			return false
+		}
+	}
+	return true
+}
+
 // ExtractRegexGroups returns the values matching the rule pattern
 func (r *Rule) ExtractRegexGroups(strategy configuration.MatchingStrategy, u *url.URL) ([]string, error) {
 	if err := ensureMatchingEngine(r, strategy); err != nil {
diff --git a/rule/rule_test.go b/rule/rule_test.go
index 568fb6f83d..dee7069b57 100644
--- a/rule/rule_test.go
+++ b/rule/rule_test.go
@@ -5,6 +5,7 @@ package rule
 
 import (
 	"encoding/json"
+	"net/http"
 	"net/url"
 	"strconv"
 	"testing"
@@ -71,7 +72,7 @@ func TestRule(t *testing.T) {
 	for ind, tcase := range tests {
 		t.Run(strconv.FormatInt(int64(ind), 10), func(t *testing.T) {
 			testFunc := func(rule Rule, strategy configuration.MatchingStrategy) {
-				matched, err := rule.IsMatching(strategy, tcase.method, mustParse(t, tcase.url), ProtocolHTTP)
+				matched, err := rule.IsMatching(strategy, tcase.method, mustParse(t, tcase.url), http.Header{}, ProtocolHTTP)
 				assert.Equal(t, tcase.expectedMatch, matched)
 				assert.Equal(t, tcase.expectedErr, err)
 			}
@@ -123,7 +124,7 @@ func TestRule1(t *testing.T) {
 	}
 	for ind, tcase := range tests {
 		t.Run(strconv.FormatInt(int64(ind), 10), func(t *testing.T) {
-			matched, err := r.IsMatching(configuration.Regexp, tcase.method, mustParse(t, tcase.url), ProtocolHTTP)
+			matched, err := r.IsMatching(configuration.Regexp, tcase.method, mustParse(t, tcase.url), http.Header{}, ProtocolHTTP)
 			assert.Equal(t, tcase.expectedMatch, matched)
 			assert.Equal(t, tcase.expectedErr, err)
 		})
@@ -165,7 +166,103 @@ func TestRuleWithCustomMethod(t *testing.T) {
 	}
 	for ind, tcase := range tests {
 		t.Run(strconv.FormatInt(int64(ind), 10), func(t *testing.T) {
-			matched, err := r.IsMatching(configuration.Regexp, tcase.method, mustParse(t, tcase.url), ProtocolHTTP)
+			matched, err := r.IsMatching(configuration.Regexp, tcase.method, mustParse(t, tcase.url), http.Header{}, ProtocolHTTP)
+			assert.Equal(t, tcase.expectedMatch, matched)
+			assert.Equal(t, tcase.expectedErr, err)
+		})
+	}
+}
+
+func TestRuleWithHeaders(t *testing.T) {
+	r := &Rule{
+		Match: &Match{
+			Methods: []string{"DELETE"},
+			URL:     "https://localhost/users/<(?!admin).*>",
+			Headers: http.Header{
+				"Content-Type":    {"application+v2.json"},
+				"x-custom-header": {"foo"},
+			},
+		},
+	}
+
+	var tests = []struct {
+		method        string
+		url           string
+		headers       http.Header
+		expectedMatch bool
+		expectedErr   error
+	}{
+		{
+			method:        "DELETE",
+			url:           "https://localhost/users/foo",
+			headers:       http.Header{},
+			expectedMatch: false,
+			expectedErr:   nil,
+		},
+		{
+			method: "DELETE",
+			url:    "https://localhost/users/foo",
+			headers: http.Header{
+				"Content-Type": {"application+v2.json"},
+			},
+			expectedMatch: false,
+			expectedErr:   nil,
+		},
+		{
+			method: "DELETE",
+			url:    "https://localhost/users/foo",
+			headers: http.Header{
+				"Content-Type": {"application+v2.json"},
+			},
+			expectedMatch: false,
+			expectedErr:   nil,
+		},
+		{
+			method: "DELETE",
+			url:    "https://localhost/users/foo",
+			headers: http.Header{
+				"Content-Type":    {"application+v2.json"},
+				"x-custom-header": {"bar"},
+			},
+			expectedMatch: false,
+			expectedErr:   nil,
+		},
+		{
+			method: "DELETE",
+			url:    "https://localhost/users/foo",
+			headers: http.Header{
+				"Content-Type":    {"application+v1.json"},
+				"x-custom-header": {"foo"},
+			},
+			expectedMatch: false,
+			expectedErr:   nil,
+		},
+		{
+			method: "DELETE",
+			url:    "https://localhost/users/foo",
+			headers: http.Header{
+				"Content-Type":        {"application+v2.json"},
+				"x-custom-header":     {"foo"},
+				"x-irrelevant-header": {"something", "not", "important"},
+			},
+			expectedMatch: true,
+			expectedErr:   nil,
+		},
+		{
+			method: "DELETE",
+			url:    "https://localhost/users/foo",
+			headers: http.Header{
+				"Content-Type":        {"application+v2.json", "application+v1.json"},
+				"x-custom-header":     {"foo", "bar"},
+				"x-irrelevant-header": {"something", "not", "important"},
+			},
+			expectedMatch: false,
+			expectedErr:   nil,
+		},
+	}
+	for ind, tcase := range tests {
+		t.Run(strconv.FormatInt(int64(ind), 10), func(t *testing.T) {
+			matched, err := r.IsMatching(configuration.Regexp, tcase.method, mustParse(t, tcase.url), tcase.headers, ProtocolHTTP)
 			assert.Equal(t, tcase.expectedMatch, matched)
 			assert.Equal(t, tcase.expectedErr, err)
 		})