diff --git a/cmd/policy/main.go b/cmd/policy/main.go
index f941473116eae58d78626bab031c280dd8e1846f..b3c9ff27db77622eb6dabdc4b4054f1349d3acab 100644
--- a/cmd/policy/main.go
+++ b/cmd/policy/main.go
@@ -87,6 +87,7 @@ func main() {
 		taskFuncs := regofunc.NewTaskFuncs(cfg.Task.Addr, httpClient)
 		ocmFuncs := regofunc.NewOcmFuncs(cfg.OCM.Addr, httpClient)
 		signerFuncs := regofunc.NewSignerFuncs(cfg.Signer.Addr, httpClient)
+		didTransformerFuncs := regofunc.NewDIDWebFuncs()
 		regofunc.Register("cacheGet", rego.Function3(cacheFuncs.CacheGetFunc()))
 		regofunc.Register("cacheSet", rego.Function4(cacheFuncs.CacheSetFunc()))
 		regofunc.Register("didResolve", rego.Function1(didResolverFuncs.ResolveFunc()))
@@ -99,6 +100,8 @@ func main() {
 		regofunc.Register("verifyProof", rego.Function1(signerFuncs.VerifyProof()))
 		regofunc.Register("ocmLoginProofInvitation", rego.Function2(ocmFuncs.GetLoginProofInvitation()))
 		regofunc.Register("ocmLoginProofResult", rego.Function1(ocmFuncs.GetLoginProofResult()))
+		regofunc.Register("didToURL", rego.Function1(didTransformerFuncs.DIDToURLFunc()))
+		regofunc.Register("urlToDID", rego.Function1(didTransformerFuncs.URLToDIDFunc()))
 	}
 
 	// subscribe the cache for policy data changes
diff --git a/internal/regofunc/did_web.go b/internal/regofunc/did_web.go
new file mode 100644
index 0000000000000000000000000000000000000000..7b403b615a3ecd08e88c24452d44fa92f42a7dbb
--- /dev/null
+++ b/internal/regofunc/did_web.go
@@ -0,0 +1,145 @@
+package regofunc
+
+import (
+	"fmt"
+	"net/url"
+	"strings"
+
+	"github.com/open-policy-agent/opa/ast"
+	"github.com/open-policy-agent/opa/rego"
+	"github.com/open-policy-agent/opa/types"
+	"gitlab.com/gaia-x/data-infrastructure-federation-services/tsa/golib/errors"
+)
+
+const (
+	didSeparator   = ":"
+	urlSeparator   = "/"
+	defaultURLPath = ".well-known"
+)
+
+type DIDWebFuncs struct{}
+
+type DID struct {
+	scheme string // scheme is always "did"
+	method string // method is the specific did method - "web" in this case
+	path   string // path is the unique URI assigned by the DID method
+}
+
+func NewDIDWebFuncs() *DIDWebFuncs {
+	return &DIDWebFuncs{}
+}
+
+func (dw *DIDWebFuncs) DIDToURLFunc() (*rego.Function, rego.Builtin1) {
+	return &rego.Function{
+			Name:    "did_to_url",
+			Decl:    types.NewFunction(types.Args(types.S), types.A),
+			Memoize: true,
+		},
+		func(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
+			var did string
+
+			if err := ast.As(a.Value, &did); err != nil {
+				return nil, fmt.Errorf("invalid DID: %s", err)
+			}
+			if did == "" {
+				return nil, errors.New("DID cannot be empty")
+			}
+
+			u, err := dw.didToURL(did)
+			if err != nil {
+				return nil, err
+			}
+
+			return ast.StringTerm(u.String()), nil
+		}
+}
+
+func (dw *DIDWebFuncs) URLToDIDFunc() (*rego.Function, rego.Builtin1) {
+	return &rego.Function{
+			Name:    "url_to_did",
+			Decl:    types.NewFunction(types.Args(types.S), types.A),
+			Memoize: true,
+		},
+		func(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
+			var u string
+
+			if err := ast.As(a.Value, &u); err != nil {
+				return nil, fmt.Errorf("invalid URL: %s", err)
+			}
+			if u == "" {
+				return nil, errors.New("URL cannot be empty")
+			}
+			uri, err := url.Parse(u)
+			if err != nil {
+				return nil, errors.New("cannot parse URL")
+			}
+			if uri.Host == "" || uri.Scheme != "https" {
+				return nil, errors.New("invalid URL for did:web method")
+			}
+
+			did := dw.urlToDID(uri)
+
+			return ast.StringTerm(did.String()), nil
+		}
+}
+
+// didToURL transforms a valid DID, created by the "did:web" Method Specification, to a URL.
+// Documentation can be found here: https://w3c-ccg.github.io/did-method-web/
+func (dw *DIDWebFuncs) didToURL(DID string) (*url.URL, error) {
+	ss := strings.Split(DID, didSeparator)
+	if len(ss) < 3 {
+		return nil, errors.New("invalid DID, host is not found")
+	}
+	if ss[0] != "did" || ss[1] != "web" {
+		return nil, errors.New("invalid DID, method is unknown")
+	}
+
+	path := defaultURLPath
+	if len(ss) > 3 {
+		path = ""
+		for i := 3; i < len(ss); i++ {
+			path = path + urlSeparator + ss[i]
+		}
+	}
+	path = path + urlSeparator + "did.json"
+
+	host, err := url.PathUnescape(ss[2])
+	if err != nil {
+		return nil, errors.New("failed to url decode host from DID")
+	}
+
+	return &url.URL{
+		Scheme: "https",
+		Host:   host,
+		Path:   path,
+	}, nil
+}
+
+// urlToDID transforms a valid URL to a DID created following the "did:web" Method Specification.
+// Documentation can be found here: https://w3c-ccg.github.io/did-method-web/
+func (dw *DIDWebFuncs) urlToDID(uri *url.URL) *DID {
+	p := strings.TrimSuffix(uri.Path, "did.json")
+	sp := strings.Split(p, urlSeparator)
+
+	path := url.QueryEscape(uri.Host)
+	for _, v := range sp {
+		if v == defaultURLPath {
+			break
+		}
+		if v == "" {
+			continue
+		}
+		path = path + didSeparator + url.QueryEscape(v)
+	}
+
+	return &DID{
+		scheme: "did",
+		method: "web",
+		path:   strings.Trim(path, didSeparator),
+	}
+}
+
+// String returns a string representation of this DID.
+func (d *DID) String() string {
+	return fmt.Sprintf("%s:%s:%s", d.scheme, d.method, d.path)
+}
diff --git a/internal/regofunc/did_web_test.go b/internal/regofunc/did_web_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4e00f0af9194386e02d2d9754a607ac95ab36cd9
--- /dev/null
+++ b/internal/regofunc/did_web_test.go
@@ -0,0 +1,142 @@
+package regofunc_test
+
+import (
+	"context"
+	"encoding/json"
+	"testing"
+
+	"github.com/open-policy-agent/opa/rego"
+	"github.com/stretchr/testify/assert"
+	"gitlab.com/gaia-x/data-infrastructure-federation-services/tsa/policy/internal/regofunc"
+)
+
+func TestDIDToURLFunc(t *testing.T) {
+	tests := []struct {
+		// test input
+		name      string
+		regoQuery string
+		// expected result
+		res     string
+		errText string
+	}{
+		{
+			name:      "DID is empty",
+			regoQuery: `did_to_url("")`,
+			errText:   "DID cannot be empty",
+		},
+		{
+			name:      "invalid DID",
+			regoQuery: `did_to_url("invalid-did")`,
+			errText:   "invalid DID, host is not found",
+		},
+		{
+			name:      "invalid DID Method",
+			regoQuery: `did_to_url("did:sov:123456qwerty")`,
+			errText:   "invalid DID, method is unknown",
+		},
+		{
+			name:      "transformation success with DID containing domain only",
+			regoQuery: `did_to_url("did:web:w3c-ccg.github.io")`,
+			res:       "\"https://w3c-ccg.github.io/.well-known/did.json\"",
+		},
+		{
+			name:      "transformation success with DID containing domain and path",
+			regoQuery: `did_to_url("did:web:w3c-ccg.github.io:user:alice")`,
+			res:       "\"https://w3c-ccg.github.io/user/alice/did.json\"",
+		},
+		{
+			name:      "transformation success with DID containing network port",
+			regoQuery: `did_to_url("did:web:example.com%3A3000:user:alice")`,
+			res:       "\"https://example.com:3000/user/alice/did.json\"",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			DIDTransformerFuncs := regofunc.NewDIDWebFuncs()
+
+			r := rego.New(
+				rego.Query(test.regoQuery),
+				rego.Function1(DIDTransformerFuncs.DIDToURLFunc()),
+				rego.StrictBuiltinErrors(true),
+			)
+			resultSet, err := r.Eval(context.Background())
+
+			if err == nil {
+				resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
+				assert.NoError(t, err)
+				assert.Equal(t, test.res, string(resultBytes))
+			} else {
+				assert.ErrorContains(t, err, test.errText)
+			}
+		})
+	}
+}
+
+func TestURLToDIDFunc(t *testing.T) {
+	tests := []struct {
+		// test input
+		name      string
+		regoQuery string
+		// expected result
+		res     string
+		errText string
+	}{
+		{
+			name:      "empty URL",
+			regoQuery: `url_to_did("")`,
+			errText:   "URL cannot be empty",
+		},
+		{
+			name:      "URL containing special characters",
+			regoQuery: `url_to_did("example.com\nH1234")`,
+			errText:   "cannot parse URL",
+		},
+		{
+			name:      "URL does not contain secure protocol (https)",
+			regoQuery: `url_to_did("example.com")`,
+			errText:   "invalid URL for did:web method",
+		},
+		{
+			name:      "URL does not contain valid domain",
+			regoQuery: `url_to_did("https://")`,
+			errText:   "invalid URL for did:web method",
+		},
+		{
+			name:      "transformation success with URL containing domain only",
+			regoQuery: `url_to_did("https://w3c-ccg.github.io/.well-known/did.json")`,
+			res:       "\"did:web:w3c-ccg.github.io\"",
+		},
+		{
+			name:      "transformation success with URL containing domain with path",
+			regoQuery: `url_to_did("https://w3c-ccg.github.io/user/alice/did.json")`,
+			res:       "\"did:web:w3c-ccg.github.io:user:alice\"",
+		},
+		{
+			name:      "transformation success with URL containing network port",
+			regoQuery: `url_to_did("https://example.com:3000/user/alice/did.json")`,
+			res:       "\"did:web:example.com%3A3000:user:alice\"",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			DIDTransformerFuncs := regofunc.NewDIDWebFuncs()
+
+			r := rego.New(
+				rego.Query(test.regoQuery),
+				rego.Function1(DIDTransformerFuncs.URLToDIDFunc()),
+				rego.StrictBuiltinErrors(true),
+			)
+			resultSet, err := r.Eval(context.Background())
+
+			if err == nil {
+				resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
+				assert.NoError(t, err)
+				assert.Equal(t, test.res, string(resultBytes))
+			} else {
+				assert.ErrorContains(t, err, test.errText)
+			}
+		})
+	}
+}