diff --git a/client/registration.go b/client/registration.go
index 2f999f0ad56862a62554c5da9931c95b901d4a45..fdc5dd0ba94857a6cdec0ce69c0dce980403a41f 100644
--- a/client/registration.go
+++ b/client/registration.go
@@ -10,6 +10,7 @@
 package client
 
 import (
+	"crypto/sha256"
 	"github.com/golang/protobuf/ptypes"
 	"github.com/golang/protobuf/ptypes/any"
 	"github.com/pkg/errors"
@@ -17,7 +18,11 @@ import (
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/comms/messages"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
 	"google.golang.org/grpc"
+	"strings"
+	"time"
 )
 
 // Client -> Registration Send Function
@@ -81,3 +86,95 @@ func (c *Comms) SendGetCurrentClientVersionMessage(
 	result := &pb.ClientVersion{}
 	return result, ptypes.UnmarshalAny(resultMsg, result)
 }
+
+// RequestNdf is used to Request an ndf from permissioning
+// Used by gateway, client, nodes and gateways
+func (c *Comms) RequestNdf(host *connect.Host,
+	message *pb.NDFHash) (*pb.NDF, error) {
+
+	// Create the Send Function
+	f := func(conn *grpc.ClientConn) (*any.Any, error) {
+		// Set up the context
+		ctx, cancel := connect.MessagingContext()
+		defer cancel()
+
+		// Send the message
+		resultMsg, err := pb.NewRegistrationClient(
+			conn).PollNdf(ctx, message)
+		if err != nil {
+			return nil, errors.New(err.Error())
+		}
+		return ptypes.MarshalAny(resultMsg)
+	}
+
+	// Execute the Send function
+	jww.TRACE.Printf("Sending Request Ndf message: %+v", message)
+	resultMsg, err := c.Send(host, f)
+	if err != nil {
+		return nil, err
+	}
+
+	result := &pb.NDF{}
+	return result, ptypes.UnmarshalAny(resultMsg, result)
+
+}
+
+// RetrieveNdf, attempts to connect to the permissioning server to retrieve the latest ndf for the notifications bot
+func (c *Comms) RetrieveNdf(currentDef *ndf.NetworkDefinition) (*ndf.NetworkDefinition, error) {
+	//Hash the notifications bot ndf for comparison with registration's ndf
+	var ndfHash []byte
+	// If the ndf passed not nil, serialize and hash it
+	if currentDef != nil {
+		//Hash the notifications bot ndf for comparison with registration's ndf
+		hash := sha256.New()
+		ndfBytes, err := currentDef.Marshal()
+		if err != nil {
+			return nil, err
+		}
+		hash.Write(ndfBytes)
+		ndfHash = hash.Sum(nil)
+	}
+	//Put the hash in a message
+	msg := &pb.NDFHash{Hash: ndfHash}
+
+	regHost, ok := c.Manager.GetHost(&id.Permissioning)
+	if !ok {
+		return nil, errors.New("Failed to find permissioning host")
+	}
+
+	//Send the hash to registration
+	response, err := c.RequestNdf(regHost, msg)
+
+	// Keep going until we get a grpc error or we get an ndf
+	for err != nil {
+		// If there is an unexpected error
+		if !strings.Contains(err.Error(), ndf.NO_NDF) {
+			// If it is not an issue with no ndf, return the error up the stack
+			errMsg := errors.Errorf("Failed to get ndf from permissioning: %v", err)
+			return nil, errMsg
+		}
+
+		// If the error is that the permissioning server is not ready, ask again
+		jww.WARN.Println("Failed to get an ndf, possibly not ready yet. Retying now...")
+		time.Sleep(250 * time.Millisecond)
+		response, err = c.RequestNdf(regHost, msg)
+
+	}
+
+	//If there was no error and the response is nil, client's ndf is up-to-date
+	if response == nil || response.Ndf == nil {
+		jww.DEBUG.Printf("Our NDF is up-to-date")
+		return nil, nil
+	}
+
+	jww.INFO.Printf("Remote NDF: %s", string(response.Ndf))
+
+	//Otherwise pull the ndf out of the response
+	updatedNdf, _, err := ndf.DecodeNDF(string(response.Ndf))
+	if err != nil {
+		//If there was an error decoding ndf
+		errMsg := errors.Errorf("Failed to decode response to ndf: %v", err)
+		return nil, errMsg
+	}
+	return updatedNdf, nil
+}
diff --git a/client/registration_test.go b/client/registration_test.go
index d2480e44d69b5bfdc556d819799b92be88593505..c3ca28dbef6a4e7951f5b6841bf091fb50d6f5d9 100644
--- a/client/registration_test.go
+++ b/client/registration_test.go
@@ -10,8 +10,10 @@ package client
 import (
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/comms/registration"
+	"gitlab.com/elixxir/comms/testutils"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
 	"testing"
 )
 
@@ -70,3 +72,120 @@ func TestSendCheckClientVersionMessage(t *testing.T) {
 		t.Errorf("CheckClientVersion: Error received: %s", err)
 	}
 }
+
+//Smoke test RequestNdf
+func TestSendGetUpdatedNDF(t *testing.T) {
+	GatewayAddress := getNextAddress()
+	testId := id.NewIdFromString("test", id.Generic, t)
+	clientId := id.NewIdFromString("client", id.Generic, t)
+
+	rg := registration.StartRegistrationServer(testId, GatewayAddress,
+		registration.NewImplementation(), nil, nil)
+	defer rg.Shutdown()
+	c, err := NewClientComms(clientId, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Can't create client comms: %+v", err)
+	}
+	manager := connect.NewManagerTesting(t)
+
+	params := connect.GetDefaultHostParams()
+	params.AuthEnabled = false
+	host, err := manager.AddHost(testId, GatewayAddress, nil, params)
+	if err != nil {
+		t.Errorf("Unable to call NewHost: %+v", err)
+	}
+
+	_, err = c.RequestNdf(host, &pb.NDFHash{})
+
+	if err != nil {
+		t.Errorf("RequestNdf: Error received: %s", err)
+	}
+}
+
+// Test that Poll NDF handles all comms errors returned properly, and that it decodes and successfully returns an ndf
+func TestProtoComms_PollNdf(t *testing.T) {
+
+	// Define a client object
+	clientId := id.NewIdFromString("client", id.Generic, t)
+	c, err := NewClientComms(clientId, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Can't create client comms: %+v", err)
+	}
+
+	mockPermServer := registration.StartRegistrationServer(&id.Permissioning, RegistrationAddr, RegistrationHandler, nil, nil)
+	defer mockPermServer.Shutdown()
+
+	newNdf := &ndf.NetworkDefinition{}
+
+	// Test that poll ndf fails if getHost returns an error
+	GetHostErrBool = false
+	RequestNdfErr = nil
+
+	_, err = c.RetrieveNdf(newNdf)
+
+	if err == nil {
+		t.Errorf("GetHost should have failed but it didnt't: %+v", err)
+		t.Fail()
+	}
+
+	// Test that pollNdf returns an error in this case
+	// This enters an infinite loop is there a way to fix this test?
+
+	// Test that pollNdf Fails if it cant decode the request msg
+	RequestNdfErr = nil
+	GetHostErrBool = true
+	NdfToreturn.Ndf = []byte(ExampleBadNdfJSON)
+	_, err = c.RetrieveNdf(newNdf)
+
+	if err == nil {
+		t.Logf("RequestNdf should have failed to parse bad ndf: %+v", err)
+		t.Fail()
+	}
+	params := connect.GetDefaultHostParams()
+	params.AuthEnabled = false
+	_, err = c.ProtoComms.AddHost(&id.Permissioning, RegistrationAddr, nil, params)
+	if err != nil {
+		t.Errorf("Failed to add permissioning as a host: %+v", err)
+	}
+
+	// Test that pollNDf Is successful with expected result
+	RequestNdfErr = nil
+	GetHostErrBool = true
+	NdfToreturn.Ndf = []byte(testutils.ExampleJSON)
+	_, err = c.RetrieveNdf(newNdf)
+	//comms.mockManager.AddHost()
+	if err != nil {
+		t.Logf("Ndf failed to parse: %+v", err)
+		t.Fail()
+	}
+
+}
+
+// Happy path
+func TestProtoComms_PollNdfRepeatedly(t *testing.T) {
+	// Define a client object
+	clientId := id.NewIdFromString("client", id.Generic, t)
+	c, err := NewClientComms(clientId, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Can't create client comms: %+v", err)
+	}
+	// Start up the mock reg server
+	mockPermServer := registration.StartRegistrationServer(&id.Permissioning, RegistrationAddrErr, RegistrationError, nil, nil)
+	defer mockPermServer.Shutdown()
+
+	// Add the host to the comms object
+	params := connect.GetDefaultHostParams()
+	params.AuthEnabled = false
+	_, err = c.ProtoComms.AddHost(&id.Permissioning, RegistrationAddrErr, nil, params)
+	if err != nil {
+		t.Errorf("Failed to add permissioning as a host: %+v", err)
+	}
+
+	newNdf := &ndf.NetworkDefinition{}
+
+	// This should hit the loop until the number of retries is satisfied in the error handler
+	_, err = c.RetrieveNdf(newNdf)
+	if err != nil {
+		t.Errorf("Expected error case, should not return non-error until attempt #5")
+	}
+}