diff --git a/bindings/broadcast.go b/bindings/broadcast.go
index 9dc521d03a88ecb523c7f2aaf9f911e144f10aa4..adcae331543392ab5d8023d3b9c1c026beacaba7 100644
--- a/bindings/broadcast.go
+++ b/bindings/broadcast.go
@@ -44,9 +44,11 @@ type ChannelDef struct {
 
 // BroadcastMessage is the bindings representation of a broadcast message.
 //
-// Example JSON:
-//  {"RoundID":42,
+// BroadcastMessage Example JSON:
+//  {
+// 	 "RoundID":42,
 //   "EphID":[0,0,0,0,0,0,24,61],
+//   "RoundURL":"https://dashboard.xx.network/rounds/25?xxmessenger=true",
 //   "Payload":"SGVsbG8sIGJyb2FkY2FzdCBmcmllbmRzIQ=="
 //  }
 type BroadcastMessage struct {
@@ -57,13 +59,16 @@ type BroadcastMessage struct {
 // BroadcastReport is the bindings representation of the info on how a broadcast
 // message was sent
 //
-// Example JSON:
-//  {"RoundID":42,
-//   "EphID":[0,0,0,0,0,0,24,61]
+// BroadcastReport Example JSON:
+//  {
+//	 "Rounds": [25, 26, 29],
+//   "EphID":[0,0,0,0,0,0,24,61],
+//   "RoundURL":"https://dashboard.xx.network/rounds/25?xxmessenger=true"
 //  }
 type BroadcastReport struct {
 	RoundsList
-	EphID ephemeral.Id
+	RoundURL string
+	EphID    ephemeral.Id
 }
 
 // BroadcastListener is the public function type bindings can use to listen for
@@ -131,6 +136,7 @@ func (c *Channel) Listen(l BroadcastListener, method int) error {
 		l.Callback(json.Marshal(&BroadcastMessage{
 			BroadcastReport: BroadcastReport{
 				RoundsList: makeRoundsList(round.ID),
+				RoundURL:   getRoundURL(round.ID),
 				EphID:      receptionID.EphId,
 			},
 			Payload: payload,
@@ -152,6 +158,7 @@ func (c *Channel) Broadcast(payload []byte) ([]byte, error) {
 		return nil, err
 	}
 	return json.Marshal(BroadcastReport{
+		RoundURL:   getRoundURL(rid.ID),
 		RoundsList: makeRoundsList(rid.ID),
 		EphID:      eid,
 	})
@@ -174,6 +181,7 @@ func (c *Channel) BroadcastAsymmetric(payload, pk []byte) ([]byte, error) {
 	}
 	return json.Marshal(BroadcastReport{
 		RoundsList: makeRoundsList(rid.ID),
+		RoundURL:   getRoundURL(rid.ID),
 		EphID:      eid,
 	})
 }
diff --git a/bindings/channels.go b/bindings/channels.go
new file mode 100644
index 0000000000000000000000000000000000000000..872512c34148c12cea6b73f61ff18104dac92b63
--- /dev/null
+++ b/bindings/channels.go
@@ -0,0 +1,665 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/channels"
+	"gitlab.com/elixxir/client/cmix/rounds"
+	"gitlab.com/elixxir/client/xxdk"
+	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
+	cryptoChannel "gitlab.com/elixxir/crypto/channel"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"sync"
+	"time"
+)
+
+////////////////////////////////////////////////////////////////////////////////
+// Singleton Tracker                                                          //
+////////////////////////////////////////////////////////////////////////////////
+
+// channelManagerTrackerSingleton is used to track ChannelsManager objects
+// so that they can be referenced by ID back over the bindings.
+var channelManagerTrackerSingleton = &channelManagerTracker{
+	tracked: make(map[int]*ChannelsManager),
+	count:   0,
+}
+
+// channelManagerTracker is a singleton used to keep track of extant
+// ChannelsManager objects, preventing race conditions created by passing it
+// over the bindings.
+type channelManagerTracker struct {
+	tracked map[int]*ChannelsManager
+	count   int
+	mux     sync.RWMutex
+}
+
+// make create a ChannelsManager from an [channels.Manager], assigns it a unique
+// ID, and adds it to the channelManagerTracker.
+func (cmt *channelManagerTracker) make(c channels.Manager) *ChannelsManager {
+	cmt.mux.Lock()
+	defer cmt.mux.Unlock()
+
+	chID := cmt.count
+	cmt.count++
+
+	cmt.tracked[chID] = &ChannelsManager{
+		api: c,
+		id:  chID,
+	}
+
+	return cmt.tracked[chID]
+}
+
+// get an ChannelsManager from the channelManagerTracker given its ID.
+func (cmt *channelManagerTracker) get(id int) (*ChannelsManager, error) {
+	cmt.mux.RLock()
+	defer cmt.mux.RUnlock()
+
+	c, exist := cmt.tracked[id]
+	if !exist {
+		return nil, errors.Errorf(
+			"Cannot get ChannelsManager for ID %d, does not exist", id)
+	}
+
+	return c, nil
+}
+
+// delete removes a ChannelsManager from the channelManagerTracker.
+func (cmt *channelManagerTracker) delete(id int) {
+	cmt.mux.Lock()
+	defer cmt.mux.Unlock()
+
+	delete(cmt.tracked, id)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Basic Channel API                                                          //
+////////////////////////////////////////////////////////////////////////////////
+
+// ChannelsManager is a bindings-layer struct that wraps a [channels.Manager]
+// interface.
+type ChannelsManager struct {
+	api channels.Manager
+	id  int
+}
+
+// GetID returns the channelManagerTracker ID for the ChannelsManager object.
+func (cm *ChannelsManager) GetID() int {
+	return cm.id
+}
+
+// NewChannelsManager constructs a ChannelsManager.
+// FIXME: This is a work in progress and should not be used an event model is
+//  implemented in the style of the bindings layer's AuthCallbacks. Remove this
+//  note when that has been done.
+//
+// Parameters:
+//  - e2eID - The tracked e2e object ID. This can be retrieved using
+//    [E2e.GetID].
+//  - udID - The tracked UD object ID. This can be retrieved using
+//    [UserDiscovery.GetID].
+func NewChannelsManager(e2eID, udID int) (*ChannelsManager, error) {
+	// Get user from singleton
+	user, err := e2eTrackerSingleton.get(e2eID)
+	if err != nil {
+		return nil, err
+	}
+
+	udMan, err := udTrackerSingleton.get(udID)
+	if err != nil {
+		return nil, err
+	}
+
+	nameService, err := udMan.api.StartChannelNameService()
+	if err != nil {
+		return nil, err
+	}
+
+	// Construct new channels manager
+	// TODO: Implement a bindings layer event model, pass that in as a parameter
+	//  or the function and pass that into here.
+	m := channels.NewManager(user.api.GetStorage().GetKV(), user.api.GetCmix(),
+		user.api.GetRng(), nameService, nil)
+
+	// Add channel to singleton and return
+	return channelManagerTrackerSingleton.make(m), nil
+}
+
+// JoinChannel joins the given channel. It will fail if the channel has already
+// been joined.
+//
+// Parameters:
+//  - channelJson - A JSON encoded [ChannelDef].
+func (cm *ChannelsManager) JoinChannel(channelJson []byte) error {
+	// Unmarshal channel definition
+	def := ChannelDef{}
+	err := json.Unmarshal(channelJson, &def)
+	if err != nil {
+		return err
+	}
+
+	// Construct ID using the embedded cryptographic information
+	channelId, err := cryptoBroadcast.NewChannelID(def.Name, def.Description,
+		def.Salt, def.PubKey)
+	if err != nil {
+		return err
+	}
+
+	// Construct public key into object
+	rsaPubKey, err := rsa.LoadPublicKeyFromPem(def.PubKey)
+	if err != nil {
+		return err
+	}
+
+	// Construct cryptographic channel object
+	channel := &cryptoBroadcast.Channel{
+		ReceptionID: channelId,
+		Name:        def.Name,
+		Description: def.Description,
+		Salt:        def.Salt,
+		RsaPubKey:   rsaPubKey,
+	}
+
+	// Join the channel using the API
+	return cm.api.JoinChannel(channel)
+}
+
+// GetChannels returns the IDs of all channels that have been joined.
+//
+// Returns:
+//  - []byte - A JSON marshalled list of IDs.
+//
+// JSON Example:
+//  {
+//    U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID",
+//    "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD"
+//  }
+func (cm *ChannelsManager) GetChannels() ([]byte, error) {
+	channelIds := cm.api.GetChannels()
+	return json.Marshal(channelIds)
+}
+
+// GetChannelId returns the ID of the channel given the channel's cryptographic
+// information.
+//
+// Parameters:
+//  - channelJson - A JSON encoded [ChannelDef]. This may be retrieved from
+//    [Channel.Get], for example.
+//
+// Returns:
+//  - []byte - A JSON encoded channel ID ([id.ID]).
+//
+// JSON Example:
+//  "dGVzdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"
+func (cm *ChannelsManager) GetChannelId(channelJson []byte) ([]byte, error) {
+	def := ChannelDef{}
+	err := json.Unmarshal(channelJson, &def)
+	if err != nil {
+		return nil, err
+	}
+
+	channelId, err := cryptoBroadcast.NewChannelID(def.Name, def.Description,
+		def.Salt, def.PubKey)
+	if err != nil {
+		return nil, err
+	}
+
+	return json.Marshal(channelId)
+}
+
+// GetChannel returns the underlying cryptographic structure for a given
+// channel.
+//
+// Parameters:
+//  - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be
+//    retrieved using ChannelsManager.GetChannelId.
+//
+// Returns:
+//  - []byte - A JSON marshalled ChannelDef.
+func (cm *ChannelsManager) GetChannel(marshalledChanId []byte) ([]byte, error) {
+	// Unmarshal ID
+	chanId, err := id.Unmarshal(marshalledChanId)
+	if err != nil {
+		return nil, err
+	}
+
+	// Retrieve channel from manager
+	def, err := cm.api.GetChannel(chanId)
+	if err != nil {
+		return nil, err
+	}
+
+	// Marshal channel
+	return json.Marshal(&ChannelDef{
+		Name:        def.Name,
+		Description: def.Description,
+		Salt:        def.Salt,
+		PubKey:      rsa.CreatePublicKeyPem(def.RsaPubKey),
+	})
+}
+
+// LeaveChannel leaves the given channel. It will return an error if the
+// channel was not previously joined.
+//
+// Parameters:
+//  - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be
+//    retrieved using ChannelsManager.GetChannelId.
+func (cm *ChannelsManager) LeaveChannel(marshalledChanId []byte) error {
+	// Unmarshal channel ID
+	channelId, err := id.Unmarshal(marshalledChanId)
+	if err != nil {
+		return err
+	}
+
+	// Leave the channel
+	return cm.api.LeaveChannel(channelId)
+}
+
+// ReplayChannel replays all messages from the channel within the network's
+// memory (~3 weeks) over the event model.
+//
+// Parameters:
+//  - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be
+//    retrieved using ChannelsManager.GetChannelId.
+func (cm *ChannelsManager) ReplayChannel(marshalledChanId []byte) error {
+
+	// Unmarshal channel ID
+	chanId, err := id.Unmarshal(marshalledChanId)
+	if err != nil {
+		return err
+	}
+
+	// Replay channel
+	return cm.api.ReplayChannel(chanId)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Channel Sending Methods & Reports                                          //
+////////////////////////////////////////////////////////////////////////////////
+
+// ChannelSendReport is the bindings' representation of the return values of
+// ChannelsManager's Send operations.
+//
+// JSON Example:
+//  {
+//    "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=",
+//    "Rounds":[1,5,9],
+//    "EphId": 0
+//  }
+type ChannelSendReport struct {
+	MessageId []byte
+	RoundsList
+	EphId int64
+}
+
+// SendGeneric is used to send a raw message over a channel. In general, it
+// should be wrapped in a function that defines the wire protocol. If the final
+// message, before being sent over the wire, is too long, this will return an
+// error. Due to the underlying encoding using compression, it isn't possible to
+// define the largest payload that can be sent, but it will always be possible
+// to send a payload of 802 bytes at minimum. The meaning of validUntil depends
+// on the use case.
+//
+// Parameters:
+//  - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be
+//    retrieved using ChannelsManager.GetChannelId.
+//  - messageType - The message type of the message. This will be a valid
+//    [channels.MessageType].
+//  - message - The contents of the message. This need not be of data type
+//    string, as the message could be a specified format that the channel may
+//    recognize.
+//  - leaseTimeMS - The lease of the message. This will be how long the message
+//    is valid until, in milliseconds. As per the channels.Manager
+//    documentation, this has different meanings depending on the use case.
+//    These use cases may be generic enough that they will not be enumerated
+//    here.
+//  - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty,
+//    and GetDefaultCMixParams will be used internally.
+//
+// Returns:
+//  - []byte - A JSON marshalled ChannelSendReport.
+func (cm *ChannelsManager) SendGeneric(marshalledChanId []byte,
+	messageType int, message []byte, leaseTimeMS int64,
+	cmixParamsJSON []byte) ([]byte, error) {
+
+	// Unmarshal channel ID and parameters
+	chanId, params, err := parseChannelsParameters(
+		marshalledChanId, cmixParamsJSON)
+	if err != nil {
+		return nil, err
+	}
+
+	// Send message
+	chanMsgId, rndId, ephId, err := cm.api.SendGeneric(chanId,
+		channels.MessageType(messageType), message,
+		time.Duration(leaseTimeMS), params.CMIX)
+	if err != nil {
+		return nil, err
+	}
+
+	// Construct send report
+	return constructChannelSendReport(chanMsgId, rndId, ephId)
+}
+
+// SendAdminGeneric is used to send a raw message over a channel encrypted with
+// admin keys, identifying it as sent by the admin. In general, it should be
+// wrapped in a function that defines the wire protocol. If the final message,
+// before being sent over the wire, is too long, this will return an error. The
+// message must be at most 510 bytes long.
+//
+// Parameters:
+//  - adminPrivateKey - The PEM-encoded admin RSA private key.
+//  - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be
+//    retrieved using ChannelsManager.GetChannelId.
+//  - messageType - The message type of the message. This will be a valid
+//    [channels.MessageType].
+//  - message - The contents of the message. The message should be at most 510
+//    bytes. This need not be of data type string, as the message could be a
+//    specified format that the channel may recognize.
+//  - leaseTimeMS - The lease of the message. This will be how long the message
+//    is valid until, in milliseconds. As per the channels.Manager
+//    documentation, this has different meanings depending on the use case.
+//    These use cases may be generic enough that they will not be enumerated
+//    here.
+//  - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty,
+//    and GetDefaultCMixParams will be used internally.
+//
+// Returns:
+//  - []byte - A JSON marshalled ChannelSendReport.
+func (cm *ChannelsManager) SendAdminGeneric(adminPrivateKey,
+	marshalledChanId []byte,
+	messageType int, message []byte, leaseTimeMS int64,
+	cmixParamsJSON []byte) ([]byte, error) {
+
+	// Load private key from file
+	rsaPrivKey, err := rsa.LoadPrivateKeyFromPem(adminPrivateKey)
+	if err != nil {
+		return nil, err
+	}
+
+	// Unmarshal channel ID and parameters
+	chanId, params, err := parseChannelsParameters(
+		marshalledChanId, cmixParamsJSON)
+	if err != nil {
+		return nil, err
+	}
+
+	// Send admin message
+	chanMsgId, rndId, ephId, err := cm.api.SendAdminGeneric(rsaPrivKey,
+		chanId, channels.MessageType(messageType), message,
+		time.Duration(leaseTimeMS), params.CMIX)
+
+	// Construct send report
+	return constructChannelSendReport(chanMsgId, rndId, ephId)
+}
+
+// SendMessage is used to send a formatted message over a channel.
+// Due to the underlying encoding using compression, it isn't possible to define
+// the largest payload that can be sent, but it will always be possible to send
+// a payload of 798 bytes at minimum.
+//
+// The message will auto delete validUntil after the round it is sent in,
+// lasting forever if [channels.ValidForever] is used.
+//
+// Parameters:
+//  - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be
+//    retrieved using ChannelsManager.GetChannelId.
+//  - message - The contents of the message. The message should be at most 510
+//    bytes. This is expected to be Unicode, and thus a string data type is
+//    expected
+//  - leaseTimeMS - The lease of the message. This will be how long the message
+//    is valid until, in milliseconds. As per the channels.Manager
+//    documentation, this has different meanings depending on the use case.
+//    These use cases may be generic enough that they will not be enumerated
+//    here.
+//  - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be
+//    empty, and GetDefaultCMixParams will be used internally.
+//
+// Returns:
+//  - []byte - A JSON marshalled ChannelSendReport
+func (cm *ChannelsManager) SendMessage(marshalledChanId []byte,
+	message string, leaseTimeMS int64, cmixParamsJSON []byte) ([]byte, error) {
+
+	// Unmarshal channel ID and parameters
+	chanId, params, err := parseChannelsParameters(
+		marshalledChanId, cmixParamsJSON)
+	if err != nil {
+		return nil, err
+	}
+
+	// Send message
+	chanMsgId, rndId, ephId, err := cm.api.SendMessage(chanId, message,
+		time.Duration(leaseTimeMS), params.CMIX)
+	if err != nil {
+		return nil, err
+	}
+
+	// Construct send report
+	return constructChannelSendReport(chanMsgId, rndId, ephId)
+}
+
+// SendReply is used to send a formatted message over a channel.
+// Due to the underlying encoding using compression, it isn't possible to define
+// the largest payload that can be sent, but it will always be possible to send
+// a payload of 766 bytes at minimum.
+//
+// If the message ID the reply is sent to does not exist, then the other side
+// will post the message as a normal message and not a reply.
+// The message will auto delete validUntil after the round it is sent in,
+// lasting forever if ValidForever is used.
+//
+// Parameters:
+//  - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be
+//    retrieved using ChannelsManager.GetChannelId.
+//  - message - The contents of the message. The message should be at most 510
+//    bytes. This is expected to be Unicode, and thus a string data type is
+//    expected.
+//  - messageToReactTo - The marshalled [channel.MessageID] of the message you
+//    wish to reply to. This may be found in the ChannelSendReport if replying
+//    to your own. Alternatively, if reacting to another user's message, you may
+//    retrieve it via the ChannelMessageReceptionCallback registered using
+//    RegisterReceiveHandler.
+//  - leaseTimeMS - The lease of the message. This will be how long the message
+//    is valid until, in milliseconds. As per the channels.Manager
+//    documentation, this has different meanings depending on the use case.
+//    These use cases may be generic enough that they will not be enumerated
+//    here.
+//  - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty,
+//    and GetDefaultCMixParams will be used internally.
+//
+// Returns:
+//  - []byte - A JSON marshalled ChannelSendReport
+func (cm *ChannelsManager) SendReply(marshalledChanId []byte,
+	message string, messageToReactTo []byte, leaseTimeMS int64,
+	cmixParamsJSON []byte) ([]byte, error) {
+
+	// Unmarshal channel ID and parameters
+	chanId, params, err := parseChannelsParameters(
+		marshalledChanId, cmixParamsJSON)
+	if err != nil {
+		return nil, err
+	}
+
+	// Unmarshal message ID
+	msgId := cryptoChannel.MessageID{}
+	copy(msgId[:], messageToReactTo)
+
+	// Send Reply
+	chanMsgId, rndId, ephId, err := cm.api.SendReply(chanId, message,
+		msgId, time.Duration(leaseTimeMS), params.CMIX)
+	if err != nil {
+		return nil, err
+	}
+
+	// Construct send report
+	return constructChannelSendReport(chanMsgId, rndId, ephId)
+}
+
+// SendReaction is used to send a reaction to a message over a channel.
+// The reaction must be a single emoji with no other characters, and will
+// be rejected otherwise.
+// Users will drop the reaction if they do not recognize the reactTo message.
+//
+// Parameters:
+//  - marshalledChanId - A JSON marshalled channel ID ([id.ID]). This may be
+//    retrieved using ChannelsManager.GetChannelId.
+//  - reaction - The user's reaction. This should be a single emoji with no
+//    other characters. As such, a Unicode string is expected.
+//  - messageToReactTo - The marshalled [channel.MessageID] of the message you
+//    wish to reply to. This may be found in the ChannelSendReport if replying
+//    to your own. Alternatively, if reacting to another user's message, you may
+//    retrieve it via the ChannelMessageReceptionCallback registered using
+//    RegisterReceiveHandler.
+//  - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty,
+//  and GetDefaultCMixParams will be used internally.
+//
+// Returns:
+//  - []byte - A JSON marshalled ChannelSendReport.
+func (cm *ChannelsManager) SendReaction(marshalledChanId []byte,
+	reaction string, messageToReactTo []byte,
+	cmixParamsJSON []byte) ([]byte, error) {
+
+	// Unmarshal channel ID and parameters
+	chanId, params, err := parseChannelsParameters(
+		marshalledChanId, cmixParamsJSON)
+	if err != nil {
+		return nil, err
+	}
+
+	// Unmarshal message ID
+	msgId := cryptoChannel.MessageID{}
+	copy(msgId[:], messageToReactTo)
+
+	// Send reaction
+	chanMsgId, rndId, ephId, err := cm.api.SendReaction(chanId,
+		reaction, msgId, params.CMIX)
+	if err != nil {
+		return nil, err
+	}
+
+	// Construct send report
+	return constructChannelSendReport(chanMsgId, rndId, ephId)
+}
+
+// parseChannelsParameters is a helper function for the Send functions. It
+// parses the channel ID and the passed in parameters into their respective
+// objects. These objects are passed into the API via the internal send
+// functions.
+func parseChannelsParameters(marshalledChanId, cmixParamsJSON []byte) (
+	*id.ID, xxdk.CMIXParams, error) {
+	// Unmarshal channel ID
+	chanId, err := id.Unmarshal(marshalledChanId)
+	if err != nil {
+		return nil, xxdk.CMIXParams{}, err
+	}
+
+	// Unmarshal cmix params
+	params, err := parseCMixParams(cmixParamsJSON)
+	if err != nil {
+		return nil, xxdk.CMIXParams{}, err
+	}
+
+	return chanId, params, nil
+}
+
+// constructChannelSendReport is a helper function which returns a JSON
+// marshalled ChannelSendReport.
+func constructChannelSendReport(channelMessageId cryptoChannel.MessageID,
+	roundId id.Round, ephId ephemeral.Id) ([]byte, error) {
+	// Construct send report
+	chanSendReport := ChannelSendReport{
+		MessageId:  channelMessageId.Bytes(),
+		RoundsList: makeRoundsList(roundId),
+		EphId:      ephId.Int64(),
+	}
+
+	// Marshal send report
+	return json.Marshal(chanSendReport)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Channel Receiving Logic and Callback Registration                          //
+////////////////////////////////////////////////////////////////////////////////
+
+// ReceivedChannelMessageReport is a report structure returned via the
+// ChannelMessageReceptionCallback. This report gives the context for the
+// channel the message was sent to and the message itself. This is returned via
+// the callback as JSON marshalled bytes.
+//
+// JSON Example:
+//  {
+//    "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+//    "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=",
+//    "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=",
+//    "MessageType": 42,
+//    "SenderUsername": "hunter2",
+//    "Content": "YmFuX2JhZFVTZXI=",
+//    "Timestamp": 1662502150335283000,
+//    "Lease": 25,
+//    "Rounds": [ 1, 4, 9],
+//  }
+type ReceivedChannelMessageReport struct {
+	ChannelId      []byte
+	MessageId      []byte
+	MessageType    int
+	SenderUsername string
+	Content        []byte
+	Timestamp      int64
+	Lease          int64
+	RoundsList
+}
+
+// ChannelMessageReceptionCallback is the callback that returns the context for
+// a channel message via the Callback.
+type ChannelMessageReceptionCallback interface {
+	Callback(receivedChannelMessageReport []byte, err error)
+}
+
+// RegisterReceiveHandler is used to register handlers for non-default message
+// types. They can be processed by modules. It is important that such modules
+// sync up with the event model implementation.
+//
+// There can only be one handler per [channels.MessageType], and this will
+// return an error on any re-registration.
+//
+// Parameters:
+//  - messageType - represents the [channels.MessageType] which will have a
+//    registered listener.
+//  - listenerCb - the callback which will be executed when a channel message
+//    of messageType is received.
+func (cm *ChannelsManager) RegisterReceiveHandler(messageType int,
+	listenerCb ChannelMessageReceptionCallback) error {
+
+	// Wrap callback around backend interface
+	cb := channels.MessageTypeReceiveMessage(
+		func(channelID *id.ID, messageID cryptoChannel.MessageID,
+			messageType channels.MessageType, senderUsername string,
+			content []byte, timestamp time.Time, lease time.Duration,
+			round rounds.Round) {
+
+			rcm := ReceivedChannelMessageReport{
+				ChannelId:      channelID.Marshal(),
+				MessageId:      messageID.Bytes(),
+				MessageType:    int(messageType),
+				SenderUsername: senderUsername,
+				Content:        content,
+				Timestamp:      timestamp.UnixNano(),
+				Lease:          int64(lease),
+				RoundsList:     makeRoundsList(round.ID),
+			}
+
+			listenerCb.Callback(json.Marshal(rcm))
+		})
+
+	// Register handler
+	return cm.api.RegisterReceiveHandler(channels.MessageType(messageType), cb)
+}
diff --git a/bindings/cmix.go b/bindings/cmix.go
index 422740df88391fcdd91e54848520d38dfeb28a8d..5b5683f8aedb3e0e57f087de749c5e090823f0c0 100644
--- a/bindings/cmix.go
+++ b/bindings/cmix.go
@@ -60,10 +60,6 @@ func NewCmix(ndfJSON, storageDir string, password []byte, registrationCode strin
 // subprocesses to perform network operations.
 func LoadCmix(storageDir string, password []byte, cmixParamsJSON []byte) (*Cmix,
 	error) {
-	if len(cmixParamsJSON) == 0 {
-		jww.WARN.Printf("cMix params not specified, using defaults...")
-		cmixParamsJSON = GetDefaultCMixParams()
-	}
 
 	params, err := parseCMixParams(cmixParamsJSON)
 	if err != nil {
diff --git a/bindings/connect.go b/bindings/connect.go
index faaffc1956afc88ef17762cf52bd22fc09ae737e..df3d4ae46c7f7c0a016ecd75fb576e0e48c98e87 100644
--- a/bindings/connect.go
+++ b/bindings/connect.go
@@ -94,6 +94,7 @@ func (c *Connection) SendE2E(mt int, payload []byte) ([]byte, error) {
 
 	sr := E2ESendReport{
 		RoundsList: makeRoundsList(sendReport.RoundList...),
+		RoundURL:   getRoundURL(sendReport.RoundList[0]),
 		MessageID:  sendReport.MessageId.Marshal(),
 		Timestamp:  sendReport.SentTime.UnixNano(),
 		KeyResidue: sendReport.KeyResidue.Marshal(),
diff --git a/bindings/delivery.go b/bindings/delivery.go
index 92ccdd759425226f206bac0f7f280fefdbd70f60..b420a58fc7d80a04609bc55dd100f58399a7d5ea 100644
--- a/bindings/delivery.go
+++ b/bindings/delivery.go
@@ -9,6 +9,7 @@ package bindings
 
 import (
 	"encoding/json"
+	"fmt"
 	"time"
 
 	"github.com/pkg/errors"
@@ -17,9 +18,33 @@ import (
 	"gitlab.com/xx_network/primitives/id"
 )
 
+// dashboardBaseURL is the base of the xx network's round dashboard URL.
+// This should be used by any type of send report's GetRoundURL method.
+var dashboardBaseURL = "https://dashboard.xx.network"
+
+// SetDashboardURL is a function which modifies the base dashboard URL
+// that is returned as part of any send report. Internally, this is defaulted
+// to "https://dashboard.xx.network". This should only be called if the user
+// explicitly wants to modify the dashboard URL. This function is not
+// thread-safe, and as such should only be called on setup.
+//
+// Parameters:
+//  - newURL - A valid URL that will be used for round look up on any send
+//    report.
+func SetDashboardURL(newURL string) {
+	dashboardBaseURL = newURL
+}
+
+// getRoundURL is a helper function which returns the specific round
+// within any type of send report, if they have a round in their RoundsList.
+// This helper function is messenger specific.
+func getRoundURL(round id.Round) string {
+	return fmt.Sprintf("%s/rounds/%d?xxmessenger=true", dashboardBaseURL, round)
+}
+
 // RoundsList contains a list of round IDs.
 //
-// Example marshalled roundList object:
+// JSON Example:
 //  [1001,1003,1006]
 type RoundsList struct {
 	Rounds []uint64
diff --git a/bindings/e2eHandler.go b/bindings/e2eHandler.go
index 8f53305e39b40a281b7bc9780d1dae091a2c9989..4b63407520f8e1aa756f2e749e4b0971fc72fdd2 100644
--- a/bindings/e2eHandler.go
+++ b/bindings/e2eHandler.go
@@ -23,15 +23,17 @@ import (
 // E2ESendReport is the bindings' representation of the return values of
 // SendE2E.
 //
-// Example E2ESendReport:
-//{
-//"Rounds": [ 1, 4, 9],
-//"MessageID": "iM34yCIr4Je8ZIzL9iAAG1UWAeDiHybxMTioMAaezvs=",
-//"Timestamp": 1661532254302612000,
-//"KeyResidue": "9q2/A69EAuFM1hFAT7Bzy5uGOQ4T6bPFF72h5PlgCWE="
-//}
+// E2ESendReport Example JSON:
+//  {
+//		"Rounds": [ 1, 4, 9],
+//      "RoundURL":"https://dashboard.xx.network/rounds/25?xxmessenger=true",
+//		"MessageID": "iM34yCIr4Je8ZIzL9iAAG1UWAeDiHybxMTioMAaezvs=",
+//		"Timestamp": 1661532254302612000,
+//		"KeyResidue": "9q2/A69EAuFM1hFAT7Bzy5uGOQ4T6bPFF72h5PlgCWE="
+//  }
 type E2ESendReport struct {
 	RoundsList
+	RoundURL   string
 	MessageID  []byte
 	Timestamp  int64
 	KeyResidue []byte
@@ -45,6 +47,19 @@ func (e *E2e) GetReceptionID() []byte {
 	return e.api.GetE2E().GetReceptionID().Marshal()
 }
 
+// DeleteContact removes a partner from E2e's storage.
+//
+// Parameters:
+//  - partnerID - the marshalled bytes of id.ID.
+func (e *E2e) DeleteContact(partnerID []byte) error {
+	partner, err := id.Unmarshal(partnerID)
+	if err != nil {
+		return err
+	}
+
+	return e.api.DeleteContact(partner)
+}
+
 // GetAllPartnerIDs returns a list of all partner IDs that the user has an E2E
 // relationship with.
 //
@@ -147,6 +162,7 @@ func (e *E2e) SendE2E(messageType int, recipientId, payload,
 
 	result := E2ESendReport{
 		RoundsList: makeRoundsList(sendReport.RoundList...),
+		RoundURL:   getRoundURL(sendReport.RoundList[0]),
 		MessageID:  sendReport.MessageId.Marshal(),
 		Timestamp:  sendReport.SentTime.UnixNano(),
 		KeyResidue: sendReport.KeyResidue.Marshal(),
diff --git a/bindings/group.go b/bindings/group.go
index 6b2041bdb548f3d3f9ccb5b6758e1be45c28faca..cd40636424304646f286bedd107b446ac343e16a 100644
--- a/bindings/group.go
+++ b/bindings/group.go
@@ -157,6 +157,7 @@ func (g *GroupChat) MakeGroup(
 	report := GroupReport{
 		Id:         grp.ID.Bytes(),
 		RoundsList: makeRoundsList(roundIDs...),
+		RoundURL:   getRoundURL(roundIDs[0]),
 		Status:     int(status),
 	}
 
@@ -199,6 +200,7 @@ func (g *GroupChat) ResendRequest(groupId []byte) ([]byte, error) {
 	report := &GroupReport{
 		Id:         grp.ID.Bytes(),
 		RoundsList: makeRoundsList(rnds...),
+		RoundURL:   getRoundURL(rnds[0]),
 		Status:     int(status),
 	}
 
@@ -269,6 +271,7 @@ func (g *GroupChat) Send(groupId, message []byte, tag string) ([]byte, error) {
 
 	// Construct send report
 	sendReport := &GroupSendReport{
+		RoundURL:   getRoundURL(round.ID),
 		RoundsList: makeRoundsList(round.ID),
 		Timestamp:  timestamp.UnixNano(),
 		MessageID:  msgID.Bytes(),
@@ -407,6 +410,7 @@ type GroupRequest interface {
 }
 
 // GroupChatProcessor manages the handling of received group chat messages.
+// The decryptedMessage field will be a JSON marshalled GroupChatMessage.
 type GroupChatProcessor interface {
 	Process(decryptedMessage, msg, receptionId []byte, ephemeralId,
 		roundId int64, err error)
@@ -419,13 +423,53 @@ type groupChatProcessor struct {
 	bindingsCb GroupChatProcessor
 }
 
+// GroupChatMessage is the bindings layer representation of the
+// [groupChat.MessageReceive].
+//
+// GroupChatMessage Example JSON:
+//  {
+//    "GroupId": "AAAAAAAJlasAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE",
+//    "SenderId": "AAAAAAAAB8gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD",
+//    "MessageId": "Zm9ydHkgZml2ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
+//    "Payload": "Zm9ydHkgZml2ZQ==",
+//    "Timestamp": 1663009269474079000
+//  }
+type GroupChatMessage struct {
+	// GroupId is the ID of the group that this message was sent on.
+	GroupId []byte
+
+	// SenderId is the ID of the sender of this message.
+	SenderId []byte
+
+	// MessageId is the ID of this group message.
+	MessageId []byte
+
+	// Payload is the content of the message.
+	Payload []byte
+
+	// Timestamp is the time this message was sent on.
+	Timestamp int64
+}
+
+// convertMessageReceive is a helper function which converts a
+// [groupChat.MessageReceive] to the bindings-layer representation GroupChatMessage.
+func convertMessageReceive(decryptedMsg gc.MessageReceive) GroupChatMessage {
+	return GroupChatMessage{
+		GroupId:   decryptedMsg.GroupID.Bytes(),
+		SenderId:  decryptedMsg.SenderID.Bytes(),
+		MessageId: decryptedMsg.ID.Bytes(),
+		Payload:   decryptedMsg.Payload,
+		Timestamp: decryptedMsg.Timestamp.UnixNano(),
+	}
+}
+
 // convertProcessor turns the input of a groupChat.Processor to the
 // binding-layer primitives equivalents within the GroupChatProcessor.Process.
 func convertGroupChatProcessor(decryptedMsg gc.MessageReceive, msg format.Message,
 	receptionID receptionID.EphemeralIdentity, round rounds.Round) (
 	decryptedMessage, message, receptionId []byte, ephemeralId, roundId int64, err error) {
 
-	decryptedMessage, err = json.Marshal(decryptedMsg)
+	decryptedMessage, err = json.Marshal(convertMessageReceive(decryptedMsg))
 	message = msg.Marshal()
 	receptionId = receptionID.Source.Marshal()
 	ephemeralId = receptionID.EphId.Int64()
@@ -451,16 +495,34 @@ func (gcp *groupChatProcessor) String() string {
 // GroupReport is returned when creating a new group and contains the ID of
 // the group, a list of rounds that the group requests were sent on, and the
 // status of the send operation.
+//
+// Example GroupReport JSON:
+//		{
+//			"Id": "AAAAAAAAAM0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE",
+//			"Rounds": [25, 64],
+//			"RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true",
+//			"Status": 1
+//		}
 type GroupReport struct {
 	Id []byte
 	RoundsList
-	Status int
+	RoundURL string
+	Status   int
 }
 
 // GroupSendReport is returned when sending a group message. It contains the
 // round ID sent on and the timestamp of the send operation.
+//
+// Example GroupSendReport JSON:
+//      {
+//  	"Rounds": [25,	64],
+//  	"RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true",
+//  	"Timestamp": 1662577352813112000,
+//  	"MessageID": "69ug6FA50UT2q6MWH3hne9PkHQ+H9DnEDsBhc0m0Aww="
+//	    }
 type GroupSendReport struct {
 	RoundsList
+	RoundURL  string
 	Timestamp int64
 	MessageID []byte
 }
diff --git a/bindings/json_test.go b/bindings/json_test.go
deleted file mode 100644
index 4dd6eeadcfe5f789aa59ef1f2a1ba7b372e2b709..0000000000000000000000000000000000000000
--- a/bindings/json_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2022 xx foundation                                             //
-//                                                                            //
-// Use of this source code is governed by a license that can be found in the  //
-// LICENSE file.                                                              //
-////////////////////////////////////////////////////////////////////////////////
-
-package bindings
-
-import (
-	"encoding/json"
-	"gitlab.com/elixxir/crypto/e2e"
-	"math/rand"
-	"testing"
-	"time"
-)
-
-func TestName(t *testing.T) {
-	rl := []uint64{1, 4, 9}
-	prng := rand.New(rand.NewSource(42))
-	rfp := make([]byte, 32)
-	prng.Read(rfp)
-	mid := e2e.NewMessageID(rfp, prng.Uint64())
-
-	randData := make([]byte, 32)
-	prng.Read(randData)
-	k := e2e.Key{}
-	copy(k[:], randData)
-	kr := e2e.NewKeyResidue(k)
-
-	report := E2ESendReport{
-		RoundsList: RoundsList{rl},
-		MessageID:  mid.Marshal(),
-		Timestamp:  time.Now().UnixNano(),
-		KeyResidue: kr.Marshal(),
-	}
-
-	marshal, _ := json.Marshal(report)
-	t.Logf("%s", marshal)
-}
diff --git a/bindings/listener.go b/bindings/listener.go
index cc2bba18222e19a6f4bbf0f3f57948e7e9899872..8eaf0faff2e58bb188a9451f624d2eae1926bd9c 100644
--- a/bindings/listener.go
+++ b/bindings/listener.go
@@ -62,6 +62,7 @@ type Message struct {
 
 	Encrypted bool
 	RoundId   int
+	RoundURL  string
 }
 
 // Hear is called to receive a message in the UI.
@@ -76,6 +77,7 @@ func (l listener) Hear(item receive.Message) {
 		Timestamp:   item.Timestamp.UnixNano(),
 		Encrypted:   item.Encrypted,
 		RoundId:     int(item.Round.ID),
+		RoundURL:    getRoundURL(item.Round.ID),
 	}
 	result, err := json.Marshal(&m)
 	if err != nil {
diff --git a/bindings/params.go b/bindings/params.go
index 79ad6201ee957e0c34c584d29dc6afe095b76ae8..f44c71edd2a13c0ae59a928f276619c43a2c1a2e 100644
--- a/bindings/params.go
+++ b/bindings/params.go
@@ -77,27 +77,42 @@ func GetDefaultE2eFileTransferParams() []byte {
 	return data
 }
 
+// parseE2eFileTransferParams is a helper function which parses a JSON
+// marshalled [e2eFileTransfer.Params].
 func parseE2eFileTransferParams(data []byte) (e2eFileTransfer.Params, error) {
 	p := &e2eFileTransfer.Params{}
 	return *p, p.UnmarshalJSON(data)
 }
 
+// parseSingleUseParams is a helper function which parses a JSON marshalled
+// [single.RequestParams].
 func parseSingleUseParams(data []byte) (single.RequestParams, error) {
 	p := &single.RequestParams{}
 	return *p, p.UnmarshalJSON(data)
 }
 
+// parseFileTransferParams is a helper function which parses a JSON marshalled
+// [fileTransfer.Params].
 func parseFileTransferParams(data []byte) (fileTransfer.Params, error) {
 	p := &fileTransfer.Params{}
 	return *p, p.UnmarshalJSON(data)
 }
 
+// parseCMixParams is a helper function which parses a JSON marshalled
+// [xxdk.CMIXParams].
 func parseCMixParams(data []byte) (xxdk.CMIXParams, error) {
+	if len(data) == 0 {
+		jww.WARN.Printf("cMix params not specified, using defaults...")
+		data = GetDefaultCMixParams()
+	}
+
 	p := &xxdk.CMIXParams{}
 	err := p.Unmarshal(data)
 	return *p, err
 }
 
+// parseE2EParams is a helper function which parses a JSON marshalled
+// [xxdk.E2EParams].
 func parseE2EParams(data []byte) (xxdk.E2EParams, error) {
 	p := &xxdk.E2EParams{}
 	err := p.Unmarshal(data)
diff --git a/bindings/single.go b/bindings/single.go
index 71c4420148dcf7049a4b4fdc911afd990b810838..d6625cd71d8fe027fa2a64ca1b146d30e6f6eef0 100644
--- a/bindings/single.go
+++ b/bindings/single.go
@@ -64,6 +64,7 @@ func TransmitSingleUse(e2eID int, recipient []byte, tag string, payload,
 		EphID:       eid.EphId.Int64(),
 		ReceptionID: eid.Source,
 		RoundsList:  makeRoundsList(rids...),
+		RoundURL:    getRoundURL(rids[0]),
 	}
 	return json.Marshal(sr)
 }
@@ -99,14 +100,16 @@ func Listen(e2eID int, tag string, cb SingleUseCallback) (Stopper, error) {
 // SingleUseSendReport is the bindings-layer struct used to represent
 // information returned by single.TransmitRequest.
 //
-// JSON example:
+// SingleUseSendReport JSON example:
 //  {
 //   "Rounds":[1,5,9],
+//   "RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true",
 //   "EphID":1655533,
 //   "ReceptionID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"}
 //  }
 type SingleUseSendReport struct {
 	RoundsList
+	RoundURL    string
 	ReceptionID *id.ID
 	EphID       int64
 }
@@ -115,9 +118,10 @@ type SingleUseSendReport struct {
 // information passed to the single.Response callback interface in response to
 // single.TransmitRequest.
 //
-// JSON example:
+// SingleUseResponseReport JSON example:
 //  {
 //   "Rounds":[1,5,9],
+//   "RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true",
 //   "Payload":"rSuPD35ELWwm5KTR9ViKIz/r1YGRgXIl5792SF8o8piZzN6sT4Liq4rUU/nfOPvQEjbfWNh/NYxdJ72VctDnWw==",
 //   "EphID":1655533,
 //   "ReceptionID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"},
@@ -125,6 +129,7 @@ type SingleUseSendReport struct {
 //  }
 type SingleUseResponseReport struct {
 	RoundsList
+	RoundURL    string
 	Payload     []byte
 	ReceptionID *id.ID
 	EphID       int64
@@ -134,16 +139,18 @@ type SingleUseResponseReport struct {
 // SingleUseCallbackReport is the bindings-layer struct used to represent
 // single -use messages received by a callback passed into single.Listen.
 //
-// JSON example:
-//  {
-//   "Rounds":[1,5,9],
-//   "Payload":"rSuPD35ELWwm5KTR9ViKIz/r1YGRgXIl5792SF8o8piZzN6sT4Liq4rUU/nfOPvQEjbfWNh/NYxdJ72VctDnWw==",
-//   "Partner":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD",
-//   "EphID":1655533,
-//   "ReceptionID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"}
-//  }
+// SingleUseCallbackReport JSON example:
+//    {
+//      "Rounds":[1,5,9],
+//      "RoundURL": "https://dashboard.xx.network/rounds/25?xxmessenger=true",
+//      "Payload":"rSuPD35ELWwm5KTR9ViKIz/r1YGRgXIl5792SF8o8piZzN6sT4Liq4rUU/nfOPvQEjbfWNh/NYxdJ72VctDnWw==",
+//      "Partner":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD",
+//      "EphID":1655533,
+//      "ReceptionID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"}
+//    }
 type SingleUseCallbackReport struct {
 	RoundsList
+	RoundURL    string
 	Payload     []byte
 	Partner     *id.ID
 	EphID       int64
@@ -208,6 +215,7 @@ func (sl singleUseListener) Callback(
 	scr := SingleUseCallbackReport{
 		Payload:     req.GetPayload(),
 		RoundsList:  makeRoundsList(rids...),
+		RoundURL:    getRoundURL(rids[0]),
 		Partner:     req.GetPartner(),
 		EphID:       eid.EphId.Int64(),
 		ReceptionID: eid.Source,
@@ -246,6 +254,7 @@ func (sr singleUseResponse) Callback(payload []byte,
 	}
 	sendReport := SingleUseResponseReport{
 		RoundsList:  makeRoundsList(rids...),
+		RoundURL:    getRoundURL(rids[0]),
 		ReceptionID: receptionID.Source,
 		EphID:       receptionID.EphId.Int64(),
 		Payload:     payload,
diff --git a/bindings/ud.go b/bindings/ud.go
index a49031ad7fd9752a99315c6606c04706cbb1b800..d167e145daf1e110bf0728cf13bc28c54287538a 100644
--- a/bindings/ud.go
+++ b/bindings/ud.go
@@ -414,6 +414,7 @@ func LookupUD(e2eID int, udContact []byte, cb UdLookupCallback,
 		EphID:       eid.EphId.Int64(),
 		ReceptionID: eid.Source,
 		RoundsList:  makeRoundsList(rids...),
+		RoundURL:    getRoundURL(rids[0]),
 	}
 
 	return json.Marshal(sr)
@@ -517,6 +518,7 @@ func SearchUD(e2eID int, udContact []byte, cb UdSearchCallback,
 		EphID:       eid.EphId.Int64(),
 		ReceptionID: eid.Source,
 		RoundsList:  makeRoundsList(rids...),
+		RoundURL:    getRoundURL(rids[0]),
 	}
 
 	return json.Marshal(sr)
diff --git a/cmd/callbacks.go b/cmd/callbacks.go
index 79ea1931d349b3c056c29ce33a6dfdbddc60cb7e..058c79dd0430b81243a4077f167694b37a909c3d 100644
--- a/cmd/callbacks.go
+++ b/cmd/callbacks.go
@@ -27,6 +27,7 @@ import (
 type authCallbacks struct {
 	autoConfirm bool
 	confCh      chan *id.ID
+	reqCh       chan *id.ID
 	params      xxdk.E2EParams
 }
 
@@ -34,6 +35,7 @@ func makeAuthCallbacks(autoConfirm bool, params xxdk.E2EParams) *authCallbacks {
 	return &authCallbacks{
 		autoConfirm: autoConfirm,
 		confCh:      make(chan *id.ID, 10),
+		reqCh:       make(chan *id.ID, 10),
 		params:      params,
 	}
 }
@@ -44,7 +46,7 @@ func (a *authCallbacks) Request(requestor contact.Contact,
 	msg := fmt.Sprintf("Authentication channel request from: %s\n",
 		requestor.ID)
 	jww.INFO.Printf(msg)
-	fmt.Printf(msg)
+	fmt.Print(msg)
 	if a.autoConfirm {
 		jww.INFO.Printf("Channel Request: %s",
 			requestor.ID)
@@ -55,8 +57,9 @@ func (a *authCallbacks) Request(requestor contact.Contact,
 		}
 
 		a.confCh <- requestor.ID
+	} else {
+		a.reqCh <- requestor.ID
 	}
-
 }
 
 func (a *authCallbacks) Confirm(requestor contact.Contact,
@@ -72,7 +75,7 @@ func (a *authCallbacks) Reset(requestor contact.Contact,
 	msg := fmt.Sprintf("Authentication channel reset from: %s\n",
 		requestor.ID)
 	jww.INFO.Printf(msg)
-	fmt.Printf(msg)
+	fmt.Print(msg)
 }
 
 func registerMessageListener(user *xxdk.E2e) chan receive.Message {
diff --git a/cmd/root.go b/cmd/root.go
index af4fa2ac28c5c716b82b7b7fa42e3f0127370040..81d197451d72494298a87613c71ec24a44c86d5f 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -14,11 +14,12 @@ import (
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
-	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	"io/ioutil"
 	"log"
 	"os"
 
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
+
 	"github.com/pkg/profile"
 
 	"strconv"
@@ -76,8 +77,13 @@ var rootCmd = &cobra.Command{
 
 		cmixParams, e2eParams := initParams()
 
-		authCbs := makeAuthCallbacks(
-			viper.GetBool(unsafeChannelCreationFlag), e2eParams)
+		autoConfirm := viper.GetBool(unsafeChannelCreationFlag)
+		acceptChannels := viper.GetBool(acceptChannelFlag)
+		if acceptChannels {
+			autoConfirm = false
+		}
+
+		authCbs := makeAuthCallbacks(autoConfirm, e2eParams)
 		user := initE2e(cmixParams, e2eParams, authCbs)
 
 		jww.INFO.Printf("Client Initialized...")
@@ -96,12 +102,14 @@ var rootCmd = &cobra.Command{
 			recipientContact = readContact(destFile)
 			recipientID = recipientContact.ID
 		} else if destId == "0" || sendId == destId {
-			jww.INFO.Printf("Sending message to self")
+			jww.INFO.Printf("Sending message to self, " +
+				"this will timeout unless authrequest is sent")
 			recipientID = receptionIdentity.ID
 			recipientContact = receptionIdentity.GetContact()
 		} else {
 			recipientID = parseRecipient(destId)
-			jww.INFO.Printf("destId: %v\nrecipientId: %v", destId, recipientID)
+			jww.INFO.Printf("destId: %v\nrecipientId: %v", destId,
+				recipientID)
 
 		}
 		isPrecanPartner := isPrecanID(recipientID)
@@ -151,11 +159,42 @@ var rootCmd = &cobra.Command{
 
 		// Send Messages
 		msgBody := viper.GetString(messageFlag)
+		hasMsgs := true
+		if msgBody == "" {
+			hasMsgs = false
+		}
 		time.Sleep(10 * time.Second)
 
 		// Accept auth request for this recipient
+		authSecs := viper.GetUint(authTimeoutFlag)
 		authConfirmed := false
-		if viper.GetBool(acceptChannelFlag) {
+		jww.INFO.Printf("Preexisting E2e partners: %+v", user.GetE2E().GetAllPartnerIDs())
+		if user.GetE2E().HasAuthenticatedChannel(recipientID) {
+			jww.INFO.Printf("Authenticated channel already in "+
+				"place for %s", recipientID)
+			authConfirmed = true
+		} else {
+			jww.INFO.Printf("No authenticated channel in "+
+				"place for %s", recipientID)
+		}
+
+		if acceptChannels && !authConfirmed {
+			for reqDone := false; !reqDone; {
+				select {
+				case reqID := <-authCbs.reqCh:
+					if recipientID.Cmp(reqID) {
+						reqDone = true
+					} else {
+						fmt.Printf(
+							"unexpected request:"+
+								" %s", reqID)
+					}
+				case <-time.After(time.Duration(authSecs) *
+					time.Second):
+					fmt.Print("timed out on auth request")
+					reqDone = true
+				}
+			}
 			// Verify that the confirmation message makes it to the
 			// original sender
 			if viper.GetBool(verifySendFlag) {
@@ -171,16 +210,6 @@ var rootCmd = &cobra.Command{
 			authConfirmed = true
 		}
 
-		jww.INFO.Printf("Preexisting E2e partners: %+v", user.GetE2E().GetAllPartnerIDs())
-		if user.GetE2E().HasAuthenticatedChannel(recipientID) {
-			jww.INFO.Printf("Authenticated channel already in "+
-				"place for %s", recipientID)
-			authConfirmed = true
-		} else {
-			jww.INFO.Printf("No authenticated channel in "+
-				"place for %s", recipientID)
-		}
-
 		// Send unsafe messages or not?
 		unsafe := viper.GetBool(unsafeFlag)
 		sendAuthReq := viper.GetBool(sendAuthRequestFlag)
@@ -200,7 +229,7 @@ var rootCmd = &cobra.Command{
 			authConfirmed = false
 		}
 
-		if !unsafe && !authConfirmed {
+		if !unsafe && !authConfirmed && hasMsgs {
 			// Signal for authConfirm callback in a separate thread
 			go func() {
 				for {
@@ -216,16 +245,15 @@ var rootCmd = &cobra.Command{
 			scnt := uint(0)
 
 			// Wait until authConfirmed
-			waitSecs := viper.GetUint(authTimeoutFlag)
-			for !authConfirmed && scnt < waitSecs {
+			for !authConfirmed && scnt < authSecs {
 				time.Sleep(1 * time.Second)
 				scnt++
 			}
-			if scnt == waitSecs {
+			if scnt == authSecs {
 				jww.FATAL.Panicf("Could not confirm "+
 					"authentication channel for %s, "+
 					"waited %d seconds.", recipientID,
-					waitSecs)
+					authSecs)
 			}
 			jww.INFO.Printf("Authentication channel confirmation"+
 				" took %d seconds", scnt)
@@ -278,6 +306,13 @@ var rootCmd = &cobra.Command{
 
 		wg := &sync.WaitGroup{}
 		sendCnt := int(viper.GetUint(sendCountFlag))
+		if !hasMsgs && sendCnt != 0 {
+			msg := "No message to send, please set your message" +
+				"or set sendCount to 0 to suppress this warning"
+			jww.WARN.Printf(msg)
+			fmt.Print(msg)
+			sendCnt = 0
+		}
 		wg.Add(sendCnt)
 		go func() {
 			sendDelay := time.Duration(viper.GetUint(sendDelayFlag))
@@ -556,7 +591,7 @@ func addAuthenticatedChannel(user *xxdk.E2e, recipientID *id.ID,
 	msg := fmt.Sprintf("Adding authenticated channel for: %s\n",
 		recipientID)
 	jww.INFO.Printf(msg)
-	fmt.Printf(msg)
+	fmt.Print(msg)
 
 	recipientContact := recipient
 
diff --git a/cmix/gateway/hostPool.go b/cmix/gateway/hostPool.go
index 49875776c2ff735520e7662eea9a14cdb3a44811..c37dbdf2fdcda4b8f9f8fac7f9339640809e40fe 100644
--- a/cmix/gateway/hostPool.go
+++ b/cmix/gateway/hostPool.go
@@ -247,7 +247,7 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator,
 		}
 	} else {
 		jww.WARN.Printf(
-			"Building new HostPool because no HostList stored: %+v", err)
+			"Building new HostPool because no HostList stored: %s", err.Error())
 	}
 
 	// Build the initial HostPool and return
diff --git a/cmix/identity/receptionID/store.go b/cmix/identity/receptionID/store.go
index 37dfb8bba2212773c6c2de62fd3604ba3d307c1e..f5b96e7eeec37ea3623a2e5f24e3b902e5d75fda 100644
--- a/cmix/identity/receptionID/store.go
+++ b/cmix/identity/receptionID/store.go
@@ -61,7 +61,7 @@ func NewOrLoadStore(kv *versioned.KV) *Store {
 	s, err := loadStore(kv)
 	if err != nil {
 		jww.WARN.Printf(
-			"ReceptionID store not found, creating a new one: %+v", err)
+			"ReceptionID store not found, creating a new one: %s", err.Error())
 
 		s = &Store{
 			active:  []*registration{},
diff --git a/e2e/parse/partition/store.go b/e2e/parse/partition/store.go
index 5ca9007a85a78f28b4566b2ec0be710f4305b0bc..713fc291a9bb530994c3598b26e595eea52a6b03 100644
--- a/e2e/parse/partition/store.go
+++ b/e2e/parse/partition/store.go
@@ -171,7 +171,7 @@ func (s *Store) loadActivePartitions() {
 	defer s.mux.Unlock()
 	obj, err := s.kv.Get(activePartitions, activePartitionVersion)
 	if err != nil {
-		jww.DEBUG.Printf("Could not load active partitions: %+v", err)
+		jww.DEBUG.Printf("Could not load active partitions: %s", err.Error())
 		return
 	}
 
diff --git a/go.mod b/go.mod
index e7ed636f1c67bc8a3fcd2c35d7dbc2f909fe6aaf..c2d7de066f1f9f4f8c9a4fb0410672fe24bd4f7f 100644
--- a/go.mod
+++ b/go.mod
@@ -14,12 +14,12 @@ require (
 	github.com/spf13/viper v1.12.0
 	github.com/stretchr/testify v1.8.0
 	gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f
-	gitlab.com/elixxir/comms v0.0.4-0.20220907184530-d8eec143a1e8
-	gitlab.com/elixxir/crypto v0.0.7-0.20220902165412-5c5e3e990e84
-	gitlab.com/elixxir/ekv v0.2.1-0.20220901224437-ab4cbf94bf8b
+	gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd
+	gitlab.com/elixxir/crypto v0.0.7-0.20220913220142-ab0771bad0af
+	gitlab.com/elixxir/ekv v0.2.1
 	gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6
-	gitlab.com/xx_network/comms v0.0.4-0.20220902164216-e3272eb0efac
-	gitlab.com/xx_network/crypto v0.0.5-0.20220902182733-69aad094b487
+	gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3
+	gitlab.com/xx_network/crypto v0.0.5-0.20220913213008-98764f5b3287
 	gitlab.com/xx_network/primitives v0.0.4-0.20220809193445-9fc0a5209548
 	go.uber.org/ratelimit v0.2.0
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
@@ -29,7 +29,7 @@ require (
 )
 
 require (
-	git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220829220442-4f51c27ab822 // indirect
+	git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220908170150-ef04339ffe65 // indirect
 	github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
 	github.com/badoux/checkmail v1.2.1 // indirect
 	github.com/cenkalti/backoff/v4 v4.1.3 // indirect
@@ -43,7 +43,6 @@ require (
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/klauspost/compress v1.11.7 // indirect
 	github.com/klauspost/cpuid/v2 v2.1.0 // indirect
-	github.com/ktr0731/grpc-web-go-client v0.2.8 // indirect
 	github.com/magiconair/properties v1.8.6 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
diff --git a/go.sum b/go.sum
index d1e34be2430ef1d0d936bd10bb5366651164bdc2..1a159e20639d8254167e431e61db4d30ec922634 100644
--- a/go.sum
+++ b/go.sum
@@ -54,8 +54,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220829220442-4f51c27ab822 h1:s2Lxh7KcUbACkGKI2SqaGTeaKdO6GCtfU6/F12Q3Hp4=
-git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220829220442-4f51c27ab822/go.mod h1:GrZ4Fy3YfaNe7RLnai+H+jE+fwqFA90tVmYOpKK90Yg=
+git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220908170150-ef04339ffe65 h1:ksB3ZiMeFplqlaCjMDqKegbGzDZdS5pU0Z5GgFeBdZ0=
+git.xx.network/elixxir/grpc-web-go-client v0.0.0-20220908170150-ef04339ffe65/go.mod h1:uFKw2wmgtlYMdiIm08dM0Vj4XvX9ZKVCj71c8O7SAPo=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
@@ -629,14 +629,14 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
 github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f h1:yXGvNBqzZwAhDYlSnxPRbgor6JWoOt1Z7s3z1O9JR40=
 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k=
-gitlab.com/elixxir/comms v0.0.4-0.20220907184530-d8eec143a1e8 h1:xbJBQdMdB+mMsKIVXqx7eAWzZmulA3KdRhlbyAk0NIc=
-gitlab.com/elixxir/comms v0.0.4-0.20220907184530-d8eec143a1e8/go.mod h1:xE9NKMAxzvTXmyJ5BMjBZFWMPfvQpw3rB7be2VYvU8E=
+gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd h1:2nHE7EoptSTBFjCxMeAveKT6urbguCwgg8Jx7XYEVe4=
+gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd/go.mod h1:AO6XkMhaHJW8eXlgL5m3UUcJqsSP8F5Wm1GX+wyq/rw=
 gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c=
 gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA=
-gitlab.com/elixxir/crypto v0.0.7-0.20220902165412-5c5e3e990e84 h1:sQCoZ+w09X/8LjQo9m7bg51IoX4AXSrXBtU8eblM/II=
-gitlab.com/elixxir/crypto v0.0.7-0.20220902165412-5c5e3e990e84/go.mod h1:Sfb4ceEoWLLYNT8V6isZa5k87NfUecM8i34Z+hDYTgg=
-gitlab.com/elixxir/ekv v0.2.1-0.20220901224437-ab4cbf94bf8b h1:0cN/WHbFaonJEN+odqyjtSrkuEbBnEnWfggT91svPGc=
-gitlab.com/elixxir/ekv v0.2.1-0.20220901224437-ab4cbf94bf8b/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU=
+gitlab.com/elixxir/crypto v0.0.7-0.20220913220142-ab0771bad0af h1:L1eOTS6m8dlCheAFOf/S3C+IcORd2R8f5qdyVRVelWA=
+gitlab.com/elixxir/crypto v0.0.7-0.20220913220142-ab0771bad0af/go.mod h1:QF8SzsrYh9Elip9EUYUDAhPjqO9DGrrrQxYHvn+VXok=
+gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is=
+gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU=
 gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
 gitlab.com/elixxir/primitives v0.0.0-20200804170709-a1896d262cd9/go.mod h1:p0VelQda72OzoUckr1O+vPW0AiFe0nyKQ6gYcmFSuF8=
 gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc=
@@ -646,13 +646,12 @@ gitlab.com/elixxir/primitives v0.0.3-0.20220810173935-592f34a88326/go.mod h1:9Bb
 gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6 h1:/cxxZBP5jTPDpC3zgOx9vV1ojmJyG8pYtkl3IbcewNQ=
 gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6/go.mod h1:9Bb2+u+CDSwsEU5Droo6saDAXuBDvLRjexpBhPAYxhA=
 gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw=
-gitlab.com/xx_network/comms v0.0.4-0.20220902164216-e3272eb0efac h1:LitOitWXN/4VNXe4dfR7Kp0reqoYmduVaExrmT1pylg=
-gitlab.com/xx_network/comms v0.0.4-0.20220902164216-e3272eb0efac/go.mod h1:S5p4aZTz1rpN27E36U+nCQbqw6ZqQfnJNeFS54DnnJ0=
+gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3 h1:7mReTvEUVoI5Qpltcmbodc/j6rdPPHDIvenY4ZmWP7o=
+gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3/go.mod h1:E2QKOKyPKLRjLUwMxgZpTKueEsHDEqshfqOHJ54ttxU=
 gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE=
 gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk=
-gitlab.com/xx_network/crypto v0.0.5-0.20220729193517-1e5e96f39f6e/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk=
-gitlab.com/xx_network/crypto v0.0.5-0.20220902182733-69aad094b487 h1:CsnSSVw2o4PXFKo4OFbivjoLA0RE/Ktx9hxvtQRGNjw=
-gitlab.com/xx_network/crypto v0.0.5-0.20220902182733-69aad094b487/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk=
+gitlab.com/xx_network/crypto v0.0.5-0.20220913213008-98764f5b3287 h1:Jd71F8f/8rieWybMqkxpKKZVVyGkeCNZWZcviGGnQ9A=
+gitlab.com/xx_network/crypto v0.0.5-0.20220913213008-98764f5b3287/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk=
 gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA=
 gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug=
 gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=
diff --git a/ud/channelIDTracking.go b/ud/channelIDTracking.go
index 65d216190e5e6126c39e9bbcb178467360877cce..25a870ee2995a9c6385568a0a04615fc989c3ce1 100644
--- a/ud/channelIDTracking.go
+++ b/ud/channelIDTracking.go
@@ -25,7 +25,6 @@ const (
 	graceDuration           = time.Hour
 )
 
-var startChannelNameServiceOnce sync.Once
 var ErrChannelLeaseSignature = errors.New("failure to validate lease signature")
 
 // loadRegistrationDisk loads a registrationDisk from the kv
@@ -355,24 +354,26 @@ func (c *clientIDTracker) requestChannelLease() (int64, []byte, error) {
 
 // StartChannelNameService creates a new clientIDTracker
 // and returns a reference to it's type as the NameService interface.
-// However it's scheduler thread isn't started until it's Start
+// However, it's scheduler thread isn't started until it's Start
 // method is called.
-func (m *Manager) StartChannelNameService() channels.NameService {
-	udPubKeyBytes := m.user.GetCmix().GetInstance().GetPartialNdf().Get().UDB.DhPubKey
-	var service channels.NameService
-	username, err := m.store.GetUsername()
-	if err != nil {
-		jww.FATAL.Panic(err)
-	}
-	startChannelNameServiceOnce.Do(func() {
-		service = newclientIDTracker(
+func (m *Manager) StartChannelNameService() (channels.NameService, error) {
+
+	if m.nameService == nil {
+		udPubKeyBytes := m.user.GetCmix().GetInstance().GetPartialNdf().Get().UDB.DhPubKey
+		username, err := m.store.GetUsername()
+		if err != nil {
+			return nil, err
+		}
+		m.nameService = newclientIDTracker(
 			m.comms,
 			m.ud.host,
 			username,
 			m.getKv(),
 			m.user.GetReceptionIdentity(),
-			ed25519.PublicKey(udPubKeyBytes),
+			udPubKeyBytes,
 			m.getRng())
-	})
-	return service
+
+	}
+
+	return m.nameService, nil
 }
diff --git a/ud/manager.go b/ud/manager.go
index 89edc765fa7820e761dd824b56dd4fb85cfdec4a..fd57a886b7046c09cfb33bf6a58adc0d752730e5 100644
--- a/ud/manager.go
+++ b/ud/manager.go
@@ -44,6 +44,10 @@ type Manager struct {
 	// ud is the tracker for the contact information of the specified UD server.
 	// This information is specified in Manager's constructors (NewOrLoad and NewManagerFromBackup).
 	ud *userDiscovery
+
+	// nameService adheres to the channels.NameService interface. This is
+	// implemented using the clientIDTracker.
+	nameService *clientIDTracker
 }
 
 // NewOrLoad loads an existing Manager from storage or creates a