diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..39a926ef3cd2803c87177c6af047204b45373e4a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+[![pipeline status](https://code.vereign.com/gaiax/tsa/infohub/badges/main/pipeline.svg)](https://code.vereign.com/gaiax/tsa/infohub/-/commits/main)
+[![coverage report](https://code.vereign.com/gaiax/tsa/infohub/badges/main/coverage.svg)](https://code.vereign.com/gaiax/tsa/infohub/-/commits/main)
+
+# Information Hub
+
+Information Hub service is responsible for exporting and importing policy data 
+wrapped in Verifiable Credentials and Verifiable Presentations.
+
diff --git a/cmd/infohub/main.go b/cmd/infohub/main.go
index 9a41fe3e3e7120b825848d499860651da93846f2..441f1734dc01399d7791708fc90b1fb8f6250e32 100644
--- a/cmd/infohub/main.go
+++ b/cmd/infohub/main.go
@@ -26,7 +26,7 @@ import (
 	goainfohub "code.vereign.com/gaiax/tsa/infohub/gen/infohub"
 	"code.vereign.com/gaiax/tsa/infohub/gen/openapi"
 	"code.vereign.com/gaiax/tsa/infohub/internal/clients/policy"
-	"code.vereign.com/gaiax/tsa/infohub/internal/clients/vault"
+	"code.vereign.com/gaiax/tsa/infohub/internal/clients/signer"
 	"code.vereign.com/gaiax/tsa/infohub/internal/config"
 	"code.vereign.com/gaiax/tsa/infohub/internal/credential"
 	"code.vereign.com/gaiax/tsa/infohub/internal/service"
@@ -72,27 +72,25 @@ func main() {
 		logger.Fatal("error connecting to database", zap.Error(err))
 	}
 
-	vault, err := vault.New(cfg.Vault.Addr, cfg.Vault.Token, cfg.Vault.Keyname)
-	if err != nil {
-		logger.Fatal("error creating vault client", zap.Error(err))
-	}
-
 	httpClient := httpClient()
-	credentials := credential.NewIssuer(cfg.Credential.IssuerName, cfg.Credential.Keyname, vault, httpClient)
+	credentials := credential.NewIssuer(cfg.Credential.IssuerURI, httpClient)
 
 	// create policy client
-	policy := policy.New(cfg.Policy.Addr, httpClient)
+	policy := policy.New(cfg.Policy.Addr, policy.WithHTTPClient(httpClient))
 
 	// create cache client
 	cache := cache.New(cfg.Cache.Addr)
 
+	// create signer client
+	signer := signer.New(cfg.Signer.Addr, signer.WithHTTPClient(httpClient))
+
 	// create services
 	var (
 		infohubSvc goainfohub.Service
 		healthSvc  goahealth.Service
 	)
 	{
-		infohubSvc = infohub.New(storage, policy, cache, credentials, logger)
+		infohubSvc = infohub.New(storage, policy, cache, credentials, signer, logger)
 		healthSvc = health.New()
 	}
 
diff --git a/internal/clients/policy/client.go b/internal/clients/policy/client.go
index 77606b2cec5460bd979716a0929ef7aa5193a7d0..0d475d4af020f2eb7cf9899a78a47e710e89cad8 100644
--- a/internal/clients/policy/client.go
+++ b/internal/clients/policy/client.go
@@ -19,11 +19,17 @@ type Client struct {
 	httpClient *http.Client
 }
 
-func New(addr string, httpClient *http.Client) *Client {
-	return &Client{
+func New(addr string, opts ...ClientOption) *Client {
+	c := &Client{
 		addr:       addr,
-		httpClient: httpClient,
+		httpClient: http.DefaultClient,
 	}
+
+	for _, opt := range opts {
+		opt(c)
+	}
+
+	return c
 }
 
 // Evaluate calls the policy service to execute the given policy.
diff --git a/internal/clients/policy/option.go b/internal/clients/policy/option.go
new file mode 100644
index 0000000000000000000000000000000000000000..60ba73264daa40a6502fbd45229a2ddf07886c98
--- /dev/null
+++ b/internal/clients/policy/option.go
@@ -0,0 +1,13 @@
+package policy
+
+import (
+	"net/http"
+)
+
+type ClientOption func(*Client)
+
+func WithHTTPClient(client *http.Client) ClientOption {
+	return func(c *Client) {
+		c.httpClient = client
+	}
+}
diff --git a/internal/clients/signer/client.go b/internal/clients/signer/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..84b18f190aa44b34f34201c4548f649bc7eb8ae3
--- /dev/null
+++ b/internal/clients/signer/client.go
@@ -0,0 +1,69 @@
+package signer
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+
+	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
+	"github.com/piprate/json-gold/ld"
+)
+
+const presentationProofPath = "/v1/presentation/proof"
+
+type Client struct {
+	addr       string
+	httpClient *http.Client
+	docLoader  *ld.CachingDocumentLoader
+}
+
+func New(addr string, opts ...ClientOption) *Client {
+	c := &Client{
+		addr:       addr,
+		httpClient: http.DefaultClient,
+	}
+
+	for _, opt := range opts {
+		opt(c)
+	}
+
+	c.docLoader = ld.NewCachingDocumentLoader(ld.NewDefaultDocumentLoader(c.httpClient))
+
+	return c
+}
+
+func (c *Client) PresentationProof(ctx context.Context, vp *verifiable.Presentation) (*verifiable.Presentation, error) {
+	vpBytes, err := json.Marshal(vp)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequestWithContext(ctx, "POST", c.addr+presentationProofPath, bytes.NewReader(vpBytes))
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := c.httpClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
+	}
+
+	respBytes, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	return verifiable.ParsePresentation(
+		respBytes,
+		verifiable.WithPresJSONLDDocumentLoader(c.docLoader),
+		verifiable.WithPresDisabledProofCheck(),
+	)
+}
diff --git a/internal/clients/signer/option.go b/internal/clients/signer/option.go
new file mode 100644
index 0000000000000000000000000000000000000000..4764ff4ef85b6235d8380757e9cee1f671f66ed1
--- /dev/null
+++ b/internal/clients/signer/option.go
@@ -0,0 +1,13 @@
+package signer
+
+import (
+	"net/http"
+)
+
+type ClientOption func(*Client)
+
+func WithHTTPClient(client *http.Client) ClientOption {
+	return func(c *Client) {
+		c.httpClient = client
+	}
+}
diff --git a/internal/config/config.go b/internal/config/config.go
index 2153cfe50775a1d8b2491a0eb42bb38caa40621b..eeb98377afde41ffa7444c695d62e2ecc6c79de6 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -7,8 +7,8 @@ type Config struct {
 	Mongo      mongoConfig
 	Policy     policyConfig
 	Cache      cacheConfig
-	Vault      vaultConfig
 	Credential credentialConfig
+	Signer     signerConfig
 
 	LogLevel string `envconfig:"LOG_LEVEL" default:"INFO"`
 }
@@ -29,15 +29,8 @@ type mongoConfig struct {
 	Collection string `envconfig:"MONGO_COLLECTION" default:"exports"`
 }
 
-type vaultConfig struct {
-	Addr    string `envconfig:"VAULT_ADDR" required:"true"`
-	Token   string `envconfig:"VAULT_TOKEN" required:"true"`
-	Keyname string `envconfig:"VAULT_KEYNAME" required:"true"`
-}
-
 type credentialConfig struct {
-	IssuerName string `envconfig:"CRED_ISSUER_NAME" required:"true"`
-	Keyname    string `envconfig:"CRED_KEYNAME" required:"true"`
+	IssuerURI string `envconfig:"ISSUER_URI" required:"true"`
 }
 
 type policyConfig struct {
@@ -47,3 +40,7 @@ type policyConfig struct {
 type cacheConfig struct {
 	Addr string `envconfig:"CACHE_ADDR" required:"true"`
 }
+
+type signerConfig struct {
+	Addr string `envconfig:"SIGNER_ADDR" required:"true"`
+}
diff --git a/internal/credential/issuer.go b/internal/credential/issuer.go
index e621865ea7c393b7a6f7725496d843ad0bc2e461..b5e14fbcf06659149b31499a486498ad0a18c506 100644
--- a/internal/credential/issuer.go
+++ b/internal/credential/issuer.go
@@ -4,48 +4,22 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/jsonld"
-	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite"
-	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/ed25519signature2018"
 	"github.com/hyperledger/aries-framework-go/pkg/doc/util"
 	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
 	"github.com/piprate/json-gold/ld"
 )
 
-type Signer interface {
-	Sign(data []byte) ([]byte, error)
-}
-
 type Issuer struct {
-	issuerName string
-	signer     Signer
-	keyname    string
-
-	// proofContext is used to generate linked data proof
-	proofContext *verifiable.LinkedDataProofContext
-	docLoader    *ld.CachingDocumentLoader
+	issuerURI string
+	docLoader *ld.CachingDocumentLoader
 }
 
-func NewIssuer(issuerName string, keyname string, signer Signer, httpClient *http.Client) *Issuer {
-	sigSuite := ed25519signature2018.New(
-		suite.WithSigner(signer),
-		suite.WithVerifier(ed25519signature2018.NewPublicKeyVerifier()))
-
-	proofContext := &verifiable.LinkedDataProofContext{
-		Suite:                   sigSuite,
-		SignatureType:           ed25519signature2018.SignatureType,
-		SignatureRepresentation: verifiable.SignatureProofValue,
-		VerificationMethod:      keyname,
-	}
-
+func NewIssuer(issuerURI string, httpClient *http.Client) *Issuer {
 	loader := ld.NewDefaultDocumentLoader(httpClient)
 
 	return &Issuer{
-		issuerName:   issuerName,
-		signer:       signer,
-		keyname:      keyname,
-		docLoader:    ld.NewCachingDocumentLoader(loader),
-		proofContext: proofContext,
+		issuerURI: issuerURI,
+		docLoader: ld.NewCachingDocumentLoader(loader),
 	}
 }
 
@@ -56,7 +30,7 @@ func (i *Issuer) NewCredential(contexts []string, subjectID string, subject map[
 	vc := &verifiable.Credential{
 		Context: jsonldContexts,
 		Types:   []string{verifiable.VCType},
-		Issuer:  verifiable.Issuer{ID: i.issuerName},
+		Issuer:  verifiable.Issuer{ID: i.issuerURI},
 		Issued:  &util.TimeWrapper{Time: time.Now()},
 		Subject: verifiable.Subject{
 			ID:           subjectID,
@@ -64,12 +38,6 @@ func (i *Issuer) NewCredential(contexts []string, subjectID string, subject map[
 		},
 	}
 
-	if proof {
-		if err := vc.AddLinkedDataProof(i.proofContext, jsonld.WithDocumentLoader(i.docLoader)); err != nil {
-			return nil, err
-		}
-	}
-
 	return vc, nil
 }
 
@@ -82,12 +50,8 @@ func (i *Issuer) NewPresentation(contexts []string, vc ...*verifiable.Credential
 		return nil, err
 	}
 	vp.Context = jsonldContexts
-	vp.ID = i.issuerName
+	vp.ID = i.issuerURI
 	vp.Type = []string{verifiable.VPType}
 
-	if err := vp.AddLinkedDataProof(i.proofContext, jsonld.WithDocumentLoader(i.docLoader)); err != nil {
-		return nil, err
-	}
-
 	return vp, nil
 }
diff --git a/internal/service/infohub/service.go b/internal/service/infohub/service.go
index b18b9e59a4299f51d1a9f932ab190d10984809ec..160426115888d7dbe8289d57bc5a1ec04c60a8c8 100644
--- a/internal/service/infohub/service.go
+++ b/internal/service/infohub/service.go
@@ -12,7 +12,7 @@ import (
 	"code.vereign.com/gaiax/tsa/infohub/internal/storage"
 )
 
-var exportAccepted = map[string]interface{}{"result": "accepted"}
+var exportAccepted = map[string]interface{}{"result": "export request is accepted"}
 
 type Storage interface {
 	ExportConfiguration(ctx context.Context, exportName string) (*storage.ExportConfiguration, error)
@@ -31,20 +31,26 @@ type Credentials interface {
 	NewPresentation(contexts []string, credentials ...*verifiable.Credential) (*verifiable.Presentation, error)
 }
 
+type Signer interface {
+	PresentationProof(ctx context.Context, vp *verifiable.Presentation) (*verifiable.Presentation, error)
+}
+
 type Service struct {
 	storage     Storage
 	policy      Policy
 	cache       Cache
 	credentials Credentials
+	signer      Signer
 	logger      *zap.Logger
 }
 
-func New(storage Storage, policy Policy, cache Cache, cred Credentials, logger *zap.Logger) *Service {
+func New(storage Storage, policy Policy, cache Cache, cred Credentials, signer Signer, logger *zap.Logger) *Service {
 	return &Service{
 		storage:     storage,
 		policy:      policy,
 		cache:       cache,
 		credentials: cred,
+		signer:      signer,
 		logger:      logger,
 	}
 }
@@ -57,27 +63,29 @@ func (s *Service) Export(ctx context.Context, req *infohub.ExportRequest) (inter
 		return nil, err
 	}
 
+	// get policy names needed for the export
+	var policyNames []string
+	for name := range exportCfg.Policies {
+		policyNames = append(policyNames, name)
+	}
+
 	// get the results of all policies configured in the export
-	results := make(map[string][]byte)
-	for policy := range exportCfg.Policies {
-		res, err := s.cache.Get(ctx, exportCacheKey(req.ExportName, policy), "", "")
-		if err != nil {
-			if errors.Is(errors.NotFound, err) {
-				if err := s.triggerExport(ctx, exportCfg); err != nil {
-					logger.Error("error performing export", zap.Error(err))
-					return nil, err
-				}
-				return exportAccepted, nil
+	policyResults, err := s.getExportData(ctx, req.ExportName, policyNames)
+	if err != nil {
+		if errors.Is(errors.NotFound, err) {
+			if err := s.triggerExport(ctx, exportCfg); err != nil {
+				logger.Error("error performing export", zap.Error(err))
+				return nil, err
 			}
-			logger.Error("failed to get policy result from cache", zap.Error(err))
-			return nil, err
+			return exportAccepted, nil
 		}
-		results[policy] = res
+		logger.Error("failed to get policy results from cache", zap.Error(err))
+		return nil, err
 	}
 
-	// create separate verifiable credential for each policy result
+	// wrap each policy result in a verifiable credential
 	var creds []*verifiable.Credential
-	for policy, result := range results {
+	for policy, result := range policyResults {
 		var res map[string]interface{}
 		if err := json.Unmarshal(result, &res); err != nil {
 			logger.Error("error decoding policy result as json", zap.Error(err))
@@ -93,16 +101,41 @@ func (s *Service) Export(ctx context.Context, req *infohub.ExportRequest) (inter
 		creds = append(creds, cred)
 	}
 
-	// bundle all credentials in a verifiable presentation with proof
+	// wrap all credentials in a verifiable presentation
 	vp, err := s.credentials.NewPresentation(exportCfg.Contexts, creds...)
 	if err != nil {
 		logger.Error("failed to create verifiable presentation", zap.Error(err))
 		return nil, errors.New("error creating export", err)
 	}
 
+	// get presentation proof from the signer
+	vp, err = s.signer.PresentationProof(ctx, vp)
+	if err != nil {
+		logger.Error("fail to get presentation proof", zap.Error(err))
+		return nil, errors.New("error creating export", err)
+	}
+
 	return vp, nil
 }
 
+// getExportData retrieves from Cache the serialized policy execution results.
+// If result for a given policy name is not found in the Cache, a NotFound error
+// is returned.
+// If all results are found, they are returned as map, where the key is policyName
+// and the value is the JSON serialized bytes of the policy result.
+func (s *Service) getExportData(ctx context.Context, exportName string, policyNames []string) (map[string][]byte, error) {
+	results := make(map[string][]byte)
+	for _, policy := range policyNames {
+		res, err := s.cache.Get(ctx, exportCacheKey(exportName, policy), "", "")
+		if err != nil {
+			return nil, err
+		}
+		results[policy] = res
+	}
+
+	return results, nil
+}
+
 func (s *Service) triggerExport(ctx context.Context, exportCfg *storage.ExportConfiguration) error {
 	s.logger.Info("export triggered", zap.String("exportName", exportCfg.ExportName))
 	for policy, input := range exportCfg.Policies {