diff --git a/broadcast/asymmetricClient_test.go b/broadcast/asymmetricClient_test.go
index d2e5a4213a93bb27df2ce58a1f3ca593a99d7553..681911a3fc9d68e9a9030afa93d6cb0e9a4d0038 100644
--- a/broadcast/asymmetricClient_test.go
+++ b/broadcast/asymmetricClient_test.go
@@ -11,7 +11,6 @@ import (
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/crypto/signature/rsa"
-	"gitlab.com/xx_network/primitives/id"
 	"reflect"
 	"sync"
 	"testing"
@@ -31,12 +30,20 @@ func Test_asymmetricClient_Smoke(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Failed to generate priv key: %+v", err)
 	}
+	cname := "MyChannel"
+	cdesc := "This is my channel about stuff."
+	csalt := cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32)
+	cpubkey := pk.GetPublic()
+	cid, err := crypto.NewChannelID(cname, cdesc, csalt, rsa.CreatePublicKeyPem(cpubkey))
+	if err != nil {
+		t.Errorf("Failed to create channel ID: %+v", err)
+	}
 	channel := crypto.Channel{
-		ReceptionID: id.NewIdFromString("ReceptionID", id.User, t),
-		Name:        "MyChannel",
-		Description: "This is my channel about stuff.",
-		Salt:        cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32),
-		RsaPubKey:   pk.GetPublic(),
+		ReceptionID: cid,
+		Name:        cname,
+		Description: cdesc,
+		Salt:        csalt,
+		RsaPubKey:   cpubkey,
 	}
 
 	const n = 5
diff --git a/broadcast/broadcastClient.go b/broadcast/broadcastClient.go
index bf31c832451d5e06c0829f6848a0b6790c3b767c..1c15e00e991dd4af8f03ac0c5d85ac6fb1610587 100644
--- a/broadcast/broadcastClient.go
+++ b/broadcast/broadcastClient.go
@@ -14,6 +14,7 @@ import (
 	"gitlab.com/elixxir/client/cmix/message"
 	crypto "gitlab.com/elixxir/crypto/broadcast"
 	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/crypto/signature/rsa"
 )
 
 // Param encapsulates configuration options for a broadcastClient
@@ -31,6 +32,17 @@ type broadcastClient struct {
 
 // NewBroadcastChannel creates a channel interface based on crypto.Channel, accepts net client connection & callback for received messages
 func NewBroadcastChannel(channel crypto.Channel, listenerCb ListenerFunc, net Client, rng *fastRNG.StreamGenerator, param Param) (Channel, error) {
+	bc := &broadcastClient{
+		channel: channel,
+		net:     net,
+		rng:     rng,
+		param:   param,
+	}
+
+	if !bc.verifyID() {
+		jww.FATAL.Panicf("Failed ID verification for broadcast channel")
+	}
+
 	// Add channel's identity
 	net.AddIdentity(channel.ReceptionID, identity.Forever, true)
 
@@ -58,12 +70,7 @@ func NewBroadcastChannel(channel crypto.Channel, listenerCb ListenerFunc, net Cl
 	jww.INFO.Printf("New %s broadcast client created for channel %q (%s)",
 		param.Method, channel.Name, channel.ReceptionID)
 
-	return &broadcastClient{
-		channel: channel,
-		net:     net,
-		rng:     rng,
-		param:   param,
-	}, nil
+	return bc, nil
 }
 
 // Stop unregisters the listener callback and stops the channel's identity
@@ -80,3 +87,14 @@ func (bc *broadcastClient) Stop() {
 func (bc *broadcastClient) Get() crypto.Channel {
 	return bc.channel
 }
+
+// verifyID generates a symmetric ID based on the info in the channel & compares it to the one passed in
+// TODO: it seems very odd to me that we do this, rather than just making the ID a private/ephemeral component like the key
+func (bc *broadcastClient) verifyID() bool {
+	gen, err := crypto.NewChannelID(bc.channel.Name, bc.channel.Description, bc.channel.Salt, rsa.CreatePublicKeyPem(bc.channel.RsaPubKey))
+	if err != nil {
+		jww.FATAL.Panicf("[verifyID] Failed to generate verified channel ID")
+		return false
+	}
+	return bc.channel.ReceptionID.Cmp(gen)
+}
diff --git a/broadcast/symmetricClient_test.go b/broadcast/symmetricClient_test.go
index d26610c54635791fbcb117802fa5de578dc7970e..a35ea5ecdca77b9abb5d874b3a28c99be6f08a7a 100644
--- a/broadcast/symmetricClient_test.go
+++ b/broadcast/symmetricClient_test.go
@@ -17,7 +17,7 @@ import (
 	cMixCrypto "gitlab.com/elixxir/crypto/cmix"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/xx_network/crypto/csprng"
-	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/crypto/signature/rsa"
 	"reflect"
 	"sync"
 	"testing"
@@ -36,12 +36,20 @@ func Test_symmetricClient_Smoke(t *testing.T) {
 	// Initialise objects used by all clients
 	cMixHandler := newMockCmixHandler()
 	rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG)
+	cname := "MyChannel"
+	cdesc := "This is my channel about stuff."
+	csalt := cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32)
+	cpubkey := newRsaPubKey(64, t)
+	cid, err := crypto.NewChannelID(cname, cdesc, csalt, rsa.CreatePublicKeyPem(cpubkey))
+	if err != nil {
+		t.Errorf("Failed to create channel ID: %+v", err)
+	}
 	channel := crypto.Channel{
-		ReceptionID: id.NewIdFromString("ReceptionID", id.User, t),
-		Name:        "MyChannel",
-		Description: "This is my channel about stuff.",
-		Salt:        cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32),
-		RsaPubKey:   newRsaPubKey(64, t),
+		ReceptionID: cid,
+		Name:        cname,
+		Description: cdesc,
+		Salt:        csalt,
+		RsaPubKey:   cpubkey,
 	}
 
 	// Set up callbacks, callback channels, and the symmetric clients
@@ -128,7 +136,7 @@ func Test_symmetricClient_Smoke(t *testing.T) {
 	}
 
 	// Broadcast payload
-	_, _, err := clients[0].Broadcast(payload, cmix.GetDefaultCMIXParams())
+	_, _, err = clients[0].Broadcast(payload, cmix.GetDefaultCMIXParams())
 	if err != nil {
 		t.Errorf("Client 0 failed to send broadcast: %+v", err)
 	}
diff --git a/cmd/root.go b/cmd/root.go
index 1d6d3c922dc686d24b47432170fca6493f3d9e7b..5d9cd10007b81a8dd2c92bfee2c819c374be9d71 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1040,9 +1040,9 @@ func init() {
 		"Ensure successful message sending by checking for round completion")
 	viper.BindPFlag("verify-sends", rootCmd.Flags().Lookup("verify-sends"))
 
-	rootCmd.Flags().UintP("receiveCount",
+	rootCmd.PersistentFlags().UintP("receiveCount",
 		"", 1, "How many messages we should wait for before quitting")
-	viper.BindPFlag("receiveCount", rootCmd.Flags().Lookup("receiveCount"))
+	viper.BindPFlag("receiveCount", rootCmd.PersistentFlags().Lookup("receiveCount"))
 	rootCmd.PersistentFlags().UintP("waitTimeout", "", 15,
 		"The number of seconds to wait for messages to arrive")
 	viper.BindPFlag("waitTimeout",
diff --git a/cmd/symmetric.go b/cmd/symmetric.go
new file mode 100644
index 0000000000000000000000000000000000000000..a2b9305939def37ab8dc9471e5cebf6432db80bf
--- /dev/null
+++ b/cmd/symmetric.go
@@ -0,0 +1,210 @@
+package cmd
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+	jww "github.com/spf13/jwalterweatherman"
+	"github.com/spf13/viper"
+	"gitlab.com/elixxir/client/broadcast"
+	"gitlab.com/elixxir/client/cmix"
+	"gitlab.com/elixxir/client/cmix/identity/receptionID"
+	"gitlab.com/elixxir/client/cmix/rounds"
+	crypto "gitlab.com/elixxir/crypto/broadcast"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/utils"
+	"os"
+	"time"
+)
+
+// singleCmd is the single-use subcommand that allows for sending and responding
+// to single-use messages.
+var symmetricCmd = &cobra.Command{
+	Use:   "symmetric",
+	Short: "Send symmetric broadcast messages",
+	Args:  cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+		client := initClient()
+
+		// Write user contact to file
+		user := client.GetUser()
+		jww.INFO.Printf("User: %s", user.ReceptionID)
+		jww.INFO.Printf("User Transmission: %s", user.TransmissionID)
+		writeContact(user.GetContact())
+
+		err := client.StartNetworkFollower(5 * time.Second)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+
+		// Wait until connected or crash on timeout
+		connected := make(chan bool, 10)
+		client.GetNetworkInterface().AddHealthCallback(
+			func(isconnected bool) {
+				connected <- isconnected
+			})
+		waitUntilConnected(connected)
+
+		// Create new symmetric or load from path if exists
+		path, err := utils.ExpandPath(viper.GetString("path"))
+		var symmetric *crypto.Symmetric
+		if utils.Exists(path) {
+			// Load symmetric from path
+			symmBytes, err := utils.ReadFile(path)
+			if err != nil {
+
+			}
+			symmetric, err = crypto.UnmarshalSymmetric(symmBytes)
+			if err != nil {
+
+			}
+		} else {
+			// New symmetric
+			name := viper.GetString("name")
+			desc := viper.GetString("description")
+			if name == "" {
+				jww.FATAL.Panicf("Name cannot be empty")
+			} else if desc == "" {
+				jww.FATAL.Panicf("description cannot be empty")
+			}
+
+			var pubKey *rsa.PublicKey
+			var salt, pubKeyBytes []byte
+			if viper.GetBool("new") {
+				privKey, err := rsa.GenerateKey(client.GetRng().GetStream(), rsa.DefaultRSABitLen)
+				if err != nil {
+
+				}
+				pubKey = privKey.GetPublic()
+				pubKeyBytes = rsa.CreatePublicKeyPem(pubKey)
+			} else {
+				pubKeyBytes := []byte(viper.GetString("rsaPub"))
+				pubKey, err = rsa.LoadPublicKeyFromPem(pubKeyBytes)
+				if err != nil {
+
+				}
+				salt = []byte(viper.GetString("salt"))
+			}
+
+			rid, err := crypto.NewSymmetricID(name, desc, salt, pubKeyBytes)
+			if err != nil {
+
+			}
+
+			symmetric = &crypto.Symmetric{
+				ReceptionID: rid,
+				Name:        name,
+				Description: desc,
+				Salt:        salt,
+				RsaPubKey:   pubKey,
+			}
+
+			symmBytes, err := symmetric.Marshal()
+			if err != nil {
+
+			}
+			// Write to file if there
+			if path != "" {
+				err = utils.WriteFile(path, symmBytes, os.ModePerm, os.ModeDir)
+				if err != nil {
+
+				}
+			} else {
+				fmt.Printf("Symmetric marshalled: %+v", symmBytes)
+			}
+		}
+
+		// Create receiver callback
+		receiveChan := make(chan []byte, 100)
+		cb := func(payload []byte,
+			receptionID receptionID.EphemeralIdentity, round rounds.Round) {
+			jww.INFO.Printf("Received symmetric message from %s over round %d", receptionID, round)
+			receiveChan <- payload
+		}
+
+		// Connect to symmetric broadcast channel
+		scl := broadcast.NewSymmetricClient(*symmetric, cb, client.GetNetworkInterface(), client.GetRng())
+		message := viper.GetString("broadcast")
+		fmt.Println(message)
+		// Send a broadcast over the channel
+		if message != "" {
+			broadcastMessage, err := broadcast.NewSizedBroadcast(scl.MaxPayloadSize(), []byte(message))
+			if err != nil {
+				jww.ERROR.Printf("Failed to create sized broadcast: %+v", err)
+			}
+			rid, eid, err := scl.Broadcast(broadcastMessage, cmix.GetDefaultCMIXParams())
+			if err != nil {
+				jww.ERROR.Printf("Failed to send symmetric broadcast message: %+v", err)
+			}
+			jww.INFO.Printf("Sent symmetric broadcast message to %s over round %d", eid, rid)
+		}
+
+		// Receive messages over the channel
+		waitSecs := viper.GetUint("waitTimeout")
+		expectedCnt := viper.GetUint("receiveCount")
+		waitTimeout := time.Duration(waitSecs) * time.Second
+		receivedCount := uint(0)
+		done := false
+		for !done && expectedCnt != 0 {
+			timeout := time.NewTimer(waitTimeout)
+			select {
+			case receivedPayload := <-receiveChan:
+				receivedCount++
+				receivedBroadcast, err := broadcast.DecodeSizedBroadcast(receivedPayload)
+				if err != nil {
+					jww.ERROR.Printf("Failed to decode sized broadcast: %+v", err)
+					continue
+				}
+				fmt.Printf("Symmetric broadcast message %d/%d received: %s\n", receivedCount, expectedCnt, string(receivedBroadcast))
+				if receivedCount == expectedCnt {
+					done = true
+				}
+			case <-timeout.C:
+				fmt.Println("Timed out")
+				jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout)
+				done = true
+			}
+		}
+
+		jww.INFO.Printf("Received %d/%d Messages!", receivedCount, expectedCnt)
+		scl.Stop()
+		err = client.StopNetworkFollower()
+		if err != nil {
+			jww.WARN.Printf(
+				"Failed to cleanly close threads: %+v\n",
+				err)
+		}
+	},
+}
+
+func init() {
+	// Single-use subcommand options
+	symmetricCmd.Flags().StringP("name", "", "",
+		"Symmetric channel name")
+	_ = viper.BindPFlag("name", symmetricCmd.Flags().Lookup("name"))
+
+	symmetricCmd.Flags().StringP("rsaPub", "", "",
+		"Symmetric channel rsa pub key")
+	_ = viper.BindPFlag("rsaPub", symmetricCmd.Flags().Lookup("rsaPub"))
+
+	symmetricCmd.Flags().StringP("salt", "", "",
+		"Symmetric channel salt")
+	_ = viper.BindPFlag("salt", symmetricCmd.Flags().Lookup("salt"))
+
+	symmetricCmd.Flags().StringP("description", "", "",
+		"Symmetric channel description")
+	_ = viper.BindPFlag("description", symmetricCmd.Flags().Lookup("description"))
+
+	symmetricCmd.Flags().StringP("path", "", "",
+		"Symmetric channel output path")
+	_ = viper.BindPFlag("path", symmetricCmd.Flags().Lookup("path"))
+
+	symmetricCmd.Flags().BoolP("new", "", false,
+		"Create new symmetric channel")
+	_ = viper.BindPFlag("new", symmetricCmd.Flags().Lookup("new"))
+
+	symmetricCmd.Flags().StringP("broadcast", "", "",
+		"Message to send via symmetric broadcast")
+	_ = viper.BindPFlag("broadcast", symmetricCmd.Flags().Lookup("broadcast"))
+
+	rootCmd.AddCommand(symmetricCmd)
+}