diff --git a/hgitaly/service/repository.py b/hgitaly/service/repository.py
index 49074aa6e1f7be9d31af1e314da1c585a64a76ea_aGdpdGFseS9zZXJ2aWNlL3JlcG9zaXRvcnkucHk=..b95ba87a8464d59b4d80df72b35ff7c2abd60ac7_aGdpdGFseS9zZXJ2aWNlL3JlcG9zaXRvcnkucHk= 100644
--- a/hgitaly/service/repository.py
+++ b/hgitaly/service/repository.py
@@ -94,6 +94,8 @@
     FullPathResponse,
     GetRawChangesRequest,
     GetRawChangesResponse,
+    GetCustomHooksRequest,
+    GetCustomHooksResponse,
     RepositoryExistsRequest,
     RepositoryExistsResponse,
     GetArchiveRequest,
@@ -108,6 +110,8 @@
     SearchFilesByContentResponse,
     SearchFilesByNameRequest,
     SearchFilesByNameResponse,
+    SetCustomHooksRequest,
+    SetCustomHooksResponse,
     SetFullPathRequest,
     SetFullPathResponse,
     WriteRefRequest,
@@ -378,8 +382,7 @@
                              context) -> SearchFilesByContentResponse:
         not_implemented(context, issue=80)  # pragma no cover
 
-    def RestoreCustomHooks(self, request: RestoreCustomHooksRequest,
-                           context) -> RestoreCustomHooksResponse:
+    def set_custom_hooks(self, request, context):
         def load_repo(req, context):
             return self.load_repo(req.repository, context)
 
@@ -383,4 +386,18 @@
         def load_repo(req, context):
             return self.load_repo(req.repository, context)
 
+        with streaming_request_tempfile_extract(
+                request, context,
+                first_request_handler=load_repo
+        ) as (repo, tmpf):
+            tmpf.flush()
+            try:
+                restore_additional(repo.ui, repo,
+                                   tmpf.name.encode('ascii'))
+            except Exception as exc:
+                context.abort(StatusCode.INTERNAL,
+                              "Error in tarball application: %r" % exc)
+
+    def RestoreCustomHooks(self, request: RestoreCustomHooksRequest,
+                           context) -> RestoreCustomHooksResponse:
         try:
@@ -386,15 +403,5 @@
         try:
-            with streaming_request_tempfile_extract(
-                    request, context,
-                    first_request_handler=load_repo
-            ) as (repo, tmpf):
-                tmpf.flush()
-                try:
-                    restore_additional(repo.ui, repo,
-                                       tmpf.name.encode('ascii'))
-                except Exception as exc:
-                    context.abort(StatusCode.INTERNAL,
-                                  "Error in tarball application: %r" % exc)
+            self.set_custom_hooks(request, context)
         finally:
             return RestoreCustomHooksResponse()
 
@@ -398,8 +405,14 @@
         finally:
             return RestoreCustomHooksResponse()
 
-    def BackupCustomHooks(self, request: BackupCustomHooksRequest,
-                          context) -> BackupCustomHooksResponse:
+    def SetCustomHooks(self, request: SetCustomHooksRequest,
+                       context) -> SetCustomHooksResponse:
+        try:
+            self.set_custom_hooks(request, context)
+        finally:
+            return SetCustomHooksResponse()
+
+    def get_custom_hooks(self, request, context, resp_cls):
         repo = self.load_repo(request.repository, context)
         with tempfile.NamedTemporaryFile(
                 mode='wb+',
@@ -414,7 +427,17 @@
                 data = tmpf.read(WRITE_BUFFER_SIZE)
                 if not data:
                     break
-                yield BackupCustomHooksResponse(data=data)
+                yield resp_cls(data=data)
+
+    def BackupCustomHooks(self, request: BackupCustomHooksRequest,
+                          context) -> BackupCustomHooksResponse:
+        yield from self.get_custom_hooks(
+            request, context, BackupCustomHooksResponse)
+
+    def GetCustomHooks(self, request: GetCustomHooksRequest,
+                       context) -> GetCustomHooksResponse:
+        yield from self.get_custom_hooks(
+            request, context, GetCustomHooksResponse)
 
     def RemoveRepository(self, request: RemoveRepositoryRequest,
                          context) -> RemoveRepositoryResponse:
diff --git a/hgitaly/service/tests/test_repository_service.py b/hgitaly/service/tests/test_repository_service.py
index 49074aa6e1f7be9d31af1e314da1c585a64a76ea_aGdpdGFseS9zZXJ2aWNlL3Rlc3RzL3Rlc3RfcmVwb3NpdG9yeV9zZXJ2aWNlLnB5..b95ba87a8464d59b4d80df72b35ff7c2abd60ac7_aGdpdGFseS9zZXJ2aWNlL3Rlc3RzL3Rlc3RfcmVwb3NpdG9yeV9zZXJ2aWNlLnB5 100644
--- a/hgitaly/service/tests/test_repository_service.py
+++ b/hgitaly/service/tests/test_repository_service.py
@@ -45,9 +45,10 @@
     FindMergeBaseRequest,
     FullPathRequest,
     GetArchiveRequest,
+    GetCustomHooksRequest,
     HasLocalBranchesRequest,
     RemoveRepositoryRequest,
     RepositoryExistsRequest,
     RestoreCustomHooksRequest,
     SearchFilesByNameRequest,
     SetFullPathRequest,
@@ -48,9 +49,10 @@
     HasLocalBranchesRequest,
     RemoveRepositoryRequest,
     RepositoryExistsRequest,
     RestoreCustomHooksRequest,
     SearchFilesByNameRequest,
     SetFullPathRequest,
+    SetCustomHooksRequest,
     WriteRefRequest,
 )
 from hgitaly.stub.repository_pb2_grpc import RepositoryServiceStub
@@ -61,6 +63,8 @@
 )
 from .fixture import ServiceFixture
 
+parametrize = pytest.mark.parametrize
+
 
 @attr.s
 class RepositoryFixture(ServiceFixture):
@@ -137,9 +141,21 @@
             for chunk in stream.slice_binary_file(tarball_path, nb_chunks)
         )
 
+    def set_custom_hooks(self, tarball_path,
+                         grpc_repo=None,
+                         nb_chunks=2,
+                         **kw):
+        if grpc_repo is None:
+            grpc_repo = self.grpc_repo
+
+        return self.stub.SetCustomHooks(
+            SetCustomHooksRequest(repository=grpc_repo, data=chunk)
+            for chunk in stream.slice_binary_file(tarball_path, nb_chunks)
+        )
+
     def backup_custom_hooks(self, save_path):
         with open(save_path, 'wb') as fobj:
             for chunk in self.stub.BackupCustomHooks(BackupCustomHooksRequest(
                     repository=self.grpc_repo)):
                 fobj.write(chunk.data)
 
@@ -140,9 +156,15 @@
     def backup_custom_hooks(self, save_path):
         with open(save_path, 'wb') as fobj:
             for chunk in self.stub.BackupCustomHooks(BackupCustomHooksRequest(
                     repository=self.grpc_repo)):
                 fobj.write(chunk.data)
 
+    def get_custom_hooks(self, save_path):
+        with open(save_path, 'wb') as fobj:
+            for chunk in self.stub.GetCustomHooks(GetCustomHooksRequest(
+                    repository=self.grpc_repo)):
+                fobj.write(chunk.data)
+
     def remove_repository(self, grpc_repo=None):
         if grpc_repo is None:
             grpc_repo = self.grpc_repo
@@ -581,7 +603,8 @@
     assert search(ref=b'unknown', query='.') == []
 
 
-def test_restore_custom_hooks(fixture_with_repo, tmpdir):
+@parametrize('rpc_name', ['restore', 'set'])
+def test_set_custom_hooks(fixture_with_repo, tmpdir, rpc_name):
     fixture = fixture_with_repo
     wrapper = fixture.repo_wrapper
 
@@ -585,6 +608,11 @@
     fixture = fixture_with_repo
     wrapper = fixture.repo_wrapper
 
+    if rpc_name == 'restore':
+        meth = fixture.restore_custom_hooks
+    else:
+        meth = fixture.set_custom_hooks
+
     # this is a valid tar file with the minimal `hgrc` used in these tests
     # the underlying restoration function from py-heptapod is itself tested,
     # and Heptapod backup tests will further exercise this.
@@ -593,7 +621,7 @@
     hgrc_path = wrapper.path / '.hg/hgrc'
 
     os.unlink(hgrc_path)
-    fixture.restore_custom_hooks(tarball_path)
+    meth(tarball_path)
     assert hgrc_path.read_text()
 
     # TODO test unknown storage and repo
@@ -603,7 +631,7 @@
     invalid_tarball_path = tmpdir / 'invalid.tgz'
     invalid_tarball_path.write("something else")
     with pytest.raises(grpc.RpcError) as exc_info:
-        fixture.restore_custom_hooks(invalid_tarball_path)
+        meth(invalid_tarball_path)
     assert exc_info.value.code() == grpc.StatusCode.INTERNAL
 
 
@@ -607,5 +635,6 @@
     assert exc_info.value.code() == grpc.StatusCode.INTERNAL
 
 
-def test_backup_custom_hooks(fixture_with_repo, tmpdir):
+@parametrize('rpc_name', ['backup', 'get'])
+def test_backup_custom_hooks(fixture_with_repo, tmpdir, rpc_name):
     fixture = fixture_with_repo
@@ -611,3 +640,7 @@
     fixture = fixture_with_repo
+    if rpc_name == 'restore':
+        meth = fixture.backup_custom_hooks
+    else:
+        meth = fixture.get_custom_hooks
 
     tarball_path = tmpdir / 'hg_aditional.tgz'
@@ -612,6 +645,7 @@
 
     tarball_path = tmpdir / 'hg_aditional.tgz'
-    fixture.backup_custom_hooks(tarball_path)
+    meth(tarball_path)
+    assert tarball_path.exists()
 
 
 def test_remove_repository(fixture_with_repo):