diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 385f857c9c947089bf9018aabca620a02a9727c1..cefd13b84113d1e2a1dc483104c11ea82a7e1819 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -41,7 +41,8 @@ wasm-test:
     - go mod vendor
     - unset SSH_PRIVATE_KEY
     - unset $(env | grep '=' | awk -F= '{print $1}' | grep -v PATH | grep -v GO | grep -v HOME)
-    - echo "WASM TESTS DISABLED FOR XX-4522, but will run them just so you can see output"
+    - rm vendor/gitlab.com/elixxir/wasm-utils/exception/throw_js.s
+    - mv vendor/gitlab.com/elixxir/wasm-utils/exception/throws.dev vendor/gitlab.com/elixxir/wasm-utils/exception/throws.go
     - GOOS=js GOARCH=wasm go test ./... -v
 
 build:
diff --git a/go.mod b/go.mod
index 0fece371169550af447d5542ce64277bccb1c379..519399caaa21d7aa6859b4edf0eb7ac2bdf9e34f 100644
--- a/go.mod
+++ b/go.mod
@@ -8,12 +8,12 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.7.0
 	github.com/spf13/jwalterweatherman v1.1.0
-	gitlab.com/elixxir/client/v4 v4.6.4-0.20230522175905-c56a71568a14
+	gitlab.com/elixxir/client/v4 v4.6.4-0.20230523181720-70dea644d559
 	gitlab.com/elixxir/crypto v0.0.7-0.20230522190154-5cbcf67f4b39
 	gitlab.com/elixxir/primitives v0.0.3-0.20230214180039-9a25e2d3969c
 	gitlab.com/elixxir/wasm-utils v0.0.0-20230522231408-a43b2c1481b2
 	gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd
-	gitlab.com/xx_network/primitives v0.0.4-0.20230310205521-c440e68e34c4
+	gitlab.com/xx_network/primitives v0.0.4-0.20230522171102-940cdd68e516
 	golang.org/x/crypto v0.5.0
 )
 
@@ -37,17 +37,20 @@ require (
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.15.9 // indirect
 	github.com/klauspost/cpuid/v2 v2.1.0 // indirect
+	github.com/kr/pretty v0.3.0 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/mattn/go-sqlite3 v1.14.15 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
 	github.com/oasisprotocol/curve25519-voi v0.0.0-20221003100820-41fad3beba17 // indirect
 	github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rs/cors v1.8.2 // indirect
 	github.com/sethvargo/go-diceware v0.3.0 // indirect
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
 	github.com/soheilhy/cmux v0.1.5 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/stretchr/testify v1.8.2 // indirect
 	github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
 	github.com/ttacon/libphonenumber v1.2.1 // indirect
 	github.com/tyler-smith/go-bip39 v1.1.0 // indirect
@@ -68,6 +71,7 @@ require (
 	google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
 	google.golang.org/grpc v1.49.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 	gorm.io/driver/sqlite v1.4.4 // indirect
 	gorm.io/gorm v1.24.3 // indirect
 	nhooyr.io/websocket v1.8.7 // indirect
diff --git a/go.sum b/go.sum
index 32f1bb72a0ab0b139ab335e2306f23d4cb29927c..c341fcefd3bfdc8377ebd1d1c15473db69be455a 100644
--- a/go.sum
+++ b/go.sum
@@ -57,6 +57,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -221,8 +222,12 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/ktr0731/grpc-test v0.1.12 h1:Yha+zH2hB48huOfbsEMfyG7FeHCrVWq4fYmHfr3iH3U=
 github.com/ktr0731/grpc-web-go-client v0.2.8 h1:nUf9p+YWirmFwmH0mwtAWhuXvzovc+/3C/eAY2Fshnk=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
@@ -329,6 +334,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
 github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
@@ -398,8 +405,8 @@ 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-20230322223210-fa84f6842de8 h1:uAFCyBkXprQoPkcDDfxXtaMyL5x+xSGrAWzR907xROQ=
 gitlab.com/elixxir/bloomfilter v0.0.0-20230322223210-fa84f6842de8/go.mod h1:1X8gRIAPDisS3W6Vtr/ymiUmZMJUIwDV1o5DEOo/pzw=
-gitlab.com/elixxir/client/v4 v4.6.4-0.20230522175905-c56a71568a14 h1:AkaecmNZ1FvATuezquU/JiOk1iG0h5NPlneriBooYJU=
-gitlab.com/elixxir/client/v4 v4.6.4-0.20230522175905-c56a71568a14/go.mod h1:4UAdP33fa6dHZpEtsaT3ijivG1anAngRM3Z0mjIchiU=
+gitlab.com/elixxir/client/v4 v4.6.4-0.20230523181720-70dea644d559 h1:F1/+kAVnCqsxHeAiX7S0wYDGF0VhfNMEGoZZayyvt2w=
+gitlab.com/elixxir/client/v4 v4.6.4-0.20230523181720-70dea644d559/go.mod h1:trGefpFqH2+kx4/uEV+yyiEWpGq3AhRMnuBQDhDQKMM=
 gitlab.com/elixxir/comms v0.0.4-0.20230519211512-4a998f4b0938 h1:f27+QUFiGWrprKm+fstOg3ABkYLpWcZi3+8Lf5eDnqY=
 gitlab.com/elixxir/comms v0.0.4-0.20230519211512-4a998f4b0938/go.mod h1:z+qW0D9VpY5QKTd7wRlb5SK4kBNqLYsa4DXBcUXue9Q=
 gitlab.com/elixxir/crypto v0.0.7-0.20230522190154-5cbcf67f4b39 h1:cU8066kdJRH88GUetdoYfT4ATg+uzSyquhHbVcbxw7Q=
@@ -414,8 +421,8 @@ gitlab.com/xx_network/comms v0.0.4-0.20230214180029-5387fb85736d h1:AZf2h0fxyO1K
 gitlab.com/xx_network/comms v0.0.4-0.20230214180029-5387fb85736d/go.mod h1:8cwPyH6G8C4qf/U5KDghn1ksOh79MrNqthjKDrfvbXY=
 gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd h1:IleH6U5D/c2zF6YL/z3cBKqBPnI5ApNMCtU7ia4t228=
 gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd/go.mod h1:PPPaFoY5Ze1qft9D0a24UHAwlvWEc2GbraihXvKYkf4=
-gitlab.com/xx_network/primitives v0.0.4-0.20230310205521-c440e68e34c4 h1:g8dsLA3tMjoix/9kZl+ELxGt/cTuuPopqUagawPwYpk=
-gitlab.com/xx_network/primitives v0.0.4-0.20230310205521-c440e68e34c4/go.mod h1:ABtt5oK+Sl1Q9l3qWK9efxmLKtNMSskpIjbe6IvB9sQ=
+gitlab.com/xx_network/primitives v0.0.4-0.20230522171102-940cdd68e516 h1:+mhYGiDuVGlTzZMvM7bAybGU85oSXffwIgYxeeBkJmE=
+gitlab.com/xx_network/primitives v0.0.4-0.20230522171102-940cdd68e516/go.mod h1:ABtt5oK+Sl1Q9l3qWK9efxmLKtNMSskpIjbe6IvB9sQ=
 gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981 h1:1s0vX9BbkiD0IVXwr3LOaTBcq1wBrWcUWMBK0s8r0Z0=
 gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM=
 gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
@@ -611,6 +618,7 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
diff --git a/indexedDb/impl/channels/callbacks.go b/indexedDb/impl/channels/callbacks.go
index 6fc2235b5515e13e92d4bbf66135bc3647a2b740..1d541bf5f23280bfc41d45d6b64f05e2ff4c6684 100644
--- a/indexedDb/impl/channels/callbacks.go
+++ b/indexedDb/impl/channels/callbacks.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/client/v4/channels"
 	"gitlab.com/elixxir/client/v4/cmix/rounds"
 	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
@@ -79,51 +80,27 @@ func (m *manager) newWASMEventModelCB(data []byte) ([]byte, error) {
 	return []byte{}, nil
 }
 
-// MessageReceived implements [bindings.ChannelUICallbacks.MessageReceived].
-func (m *manager) MessageReceived(uuid int64, channelID []byte, update bool) {
-	// Package parameters for sending
-	msg := &wChannels.MessageReceivedCallbackMessage{
-		UUID:      uuid,
-		ChannelID: channelID,
-		Update:    update,
+// EventUpdate implements [bindings.ChannelUICallbacks.EventUpdate].
+func (m *manager) EventUpdate(eventType int64, jsonData []byte) {
+	var callbackTag worker.Tag
+	isValid := false
+	switch eventType {
+	case bindings.MessageReceived:
+		callbackTag = wChannels.MessageReceivedCallbackTag
+		isValid = true
+	case bindings.MessageDeleted:
+		callbackTag = wChannels.DeletedMessageCallbackTag
+		isValid = true
+	case bindings.UserMuted:
+		callbackTag = wChannels.MutedUserCallbackTag
+		isValid = true
+	default:
+		jww.ERROR.Printf("invalid indexedDB EventUpdate type %d: %+v)",
+			eventType, jsonData)
+	}
+	if isValid {
+		m.wtm.SendMessage(callbackTag, jsonData)
 	}
-	data, err := json.Marshal(msg)
-	if err != nil {
-		jww.ERROR.Printf("Could not JSON marshal %T: %+v", msg, err)
-		return
-	}
-
-	// Send it to the main thread
-	m.wtm.SendMessage(wChannels.MessageReceivedCallbackTag, data)
-}
-
-// MessageDeleted implements [bindings.ChannelUICallbacks.MessageDeleted].
-func (m *manager) MessageDeleted(messageID []byte) {
-	m.wtm.SendMessage(wChannels.DeletedMessageCallbackTag, messageID)
-}
-
-// UserMuted implements [bindings.ChannelUICallbacks.UserMuted].
-func (m *manager) UserMuted(channelID, pubKey []byte, unmute bool) {
-	// Package parameters for sending
-	msg := &wChannels.MuteUserMessage{
-		ChannelID: channelID,
-		PubKey:    pubKey,
-		Unmute:    unmute,
-	}
-	data, err := json.Marshal(msg)
-	if err != nil {
-		jww.ERROR.Printf("Could not JSON marshal %T: %+v", msg, err)
-		return
-	}
-
-	// Send it to the main thread
-	m.wtm.SendMessage(wChannels.MutedUserCallbackTag, data)
-}
-
-// NicknameUpdate implements [bindings.ChannelUICallbacks.NicknameUpdate]
-func (m *manager) NicknameUpdate(channelIdBytes []byte, nickname string,
-	exists bool) {
-	jww.FATAL.Panicf("unimplemented")
 }
 
 // joinChannelCB is the callback for wasmModel.JoinChannel. Always returns nil;
diff --git a/indexedDb/impl/channels/implementation.go b/indexedDb/impl/channels/implementation.go
index 86c77f6acfc3fd6147c39f3b6c92bcb9efd49a11..1f69c17fe00360b8f13420c6fb9678af0f63c698 100644
--- a/indexedDb/impl/channels/implementation.go
+++ b/indexedDb/impl/channels/implementation.go
@@ -36,9 +36,9 @@ import (
 // NOTE: This model is NOT thread safe - it is the responsibility of the
 // caller to ensure that its methods are called sequentially.
 type wasmModel struct {
-	db     *idb.Database
-	cipher cryptoChannel.Cipher
-	cbs    bindings.ChannelUICallbacks
+	db          *idb.Database
+	cipher      cryptoChannel.Cipher
+	eventUpdate func(eventType int64, jsonMarshallable any)
 }
 
 // JoinChannel is called whenever a channel is joined locally.
@@ -170,7 +170,7 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID message.ID,
 		return 0
 	}
 
-	go w.cbs.MessageReceived(int64(uuid), channelIDBytes, false)
+	w.sendReceiveMessageUpdate(uuid, channelID, false)
 	return uuid
 }
 
@@ -208,7 +208,8 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, messageID,
 		jww.ERROR.Printf("Failed to receive reply: %+v", err)
 		return 0
 	}
-	go w.cbs.MessageReceived(int64(uuid), channelIDBytes, false)
+
+	w.sendReceiveMessageUpdate(uuid, channelID, false)
 	return uuid
 }
 
@@ -246,7 +247,7 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID,
 		jww.ERROR.Printf("Failed to receive reaction: %+v", err)
 		return 0
 	}
-	go w.cbs.MessageReceived(int64(uuid), channelIDBytes, false)
+	w.sendReceiveMessageUpdate(uuid, channelID, false)
 	return uuid
 }
 
@@ -393,8 +394,7 @@ func (w *wasmModel) updateMessage(currentMsg *Message, messageID *message.ID,
 	if err != nil {
 		return 0, err
 	}
-	go w.cbs.MessageReceived(int64(uuid), currentMsg.ChannelID, true)
-
+	w.sendReceiveMessageUpdate(uuid, (*id.ID)(currentMsg.ChannelID), true)
 	return uuid, nil
 }
 
@@ -491,15 +491,48 @@ func (w *wasmModel) DeleteMessage(messageID message.ID) error {
 		return err
 	}
 
-	go w.cbs.MessageDeleted(messageID.Bytes())
-
+	eventData, err := json.Marshal(bindings.MessageDeletedJson{
+		MessageID: messageID,
+	})
+	if err != nil {
+		jww.WARN.Printf("couldn't marshal MessageDeleted: %s, %+v",
+			messageID, err)
+	} else {
+		go w.eventUpdate(bindings.MessageDeleted, eventData)
+	}
 	return nil
 }
 
 // MuteUser is called whenever a user is muted or unmuted.
 func (w *wasmModel) MuteUser(
 	channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) {
-	go w.cbs.UserMuted(channelID.Marshal(), pubKey, unmute)
+	eventData, err := json.Marshal(bindings.UserMutedJson{
+		ChannelID: channelID,
+		PubKey:    pubKey,
+		Unmute:    unmute,
+	})
+	if err != nil {
+		jww.WARN.Printf("couldn't marshal UserMuted: %s, %+v",
+			pubKey, err)
+	} else {
+		go w.eventUpdate(bindings.UserMuted, eventData)
+	}
+}
+
+func (w *wasmModel) sendReceiveMessageUpdate(uuid uint64, channelID *id.ID,
+	update bool) {
+	eventMsg := bindings.MessageReceivedJson{
+		Uuid:      int64(uuid),
+		ChannelID: channelID,
+		Update:    update,
+	}
+	eventData, err := json.Marshal(eventMsg)
+	if err != nil {
+		jww.WARN.Printf("couldn't marshal MessageReceive: %v, %+v",
+			eventMsg, err)
+	} else {
+		go w.eventUpdate(bindings.MessageReceived, eventData)
+	}
 }
 
 // valueToMessage is a helper for converting js.Value to Message.
diff --git a/indexedDb/impl/channels/implementation_test.go b/indexedDb/impl/channels/implementation_test.go
index d13f725d02d33ddbd12cedea8940211fe958890f..9688c6714c6da34a5395e328865745b311580a30 100644
--- a/indexedDb/impl/channels/implementation_test.go
+++ b/indexedDb/impl/channels/implementation_test.go
@@ -24,6 +24,7 @@ import (
 
 	"github.com/hack-pad/go-indexeddb/idb"
 	jww "github.com/spf13/jwalterweatherman"
+	"github.com/stretchr/testify/require"
 
 	"gitlab.com/elixxir/client/v4/channels"
 	"gitlab.com/elixxir/client/v4/cmix/rounds"
@@ -44,12 +45,7 @@ func TestMain(m *testing.M) {
 
 type dummyCbs struct{}
 
-func (c *dummyCbs) MessageReceived(uuid int64, channelID []byte, update bool) {}
-func (c *dummyCbs) UserMuted(channelID []byte, pubKey []byte, unmute bool)    {}
-func (c *dummyCbs) MessageDeleted(messageId []byte)                           {}
-func (c *dummyCbs) NicknameUpdate(channelIdBytes []byte, nickname string,
-	exists bool) {
-}
+func (c *dummyCbs) EventUpdate(eventType int64, dataJson []byte) {}
 
 // Happy path test for receiving, updating, getting, and deleting a File.
 func TestWasmModel_ReceiveFile(t *testing.T) {
@@ -233,8 +229,12 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) {
 				t.Fatal(err)
 			}
 
+			cid, err := id.NewRandomID(csprng.NewSystemRNG(),
+				id.DummyUser.GetType())
+			require.NoError(t, err)
+
 			// Store a test message
-			testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
+			testMsg := buildMessage(cid.Bytes(), testMsgId.Bytes(), nil,
 				testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0,
 				netTime.Now(), time.Second, 0, 0, false, false, channels.Sent)
 			uuid, err2 := eventModel.upsertMessage(testMsg)
diff --git a/indexedDb/impl/channels/init.go b/indexedDb/impl/channels/init.go
index 3d97e3e4ee1d0f3cc217196e0084d278bc3dee0b..48f5359f9b61c67b42533a42fa37cec4c3c917e7 100644
--- a/indexedDb/impl/channels/init.go
+++ b/indexedDb/impl/channels/init.go
@@ -10,6 +10,7 @@
 package main
 
 import (
+	"encoding/json"
 	"syscall/js"
 
 	"github.com/hack-pad/go-indexeddb/idb"
@@ -29,13 +30,13 @@ const currentVersion uint = 1
 // The name should be a base64 encoding of the users public key. Returns the
 // EventModel based on IndexedDb and the database name as reported by IndexedDb.
 func NewWASMEventModel(databaseName string, encryption cryptoChannel.Cipher,
-	channelsCbs bindings.ChannelUICallbacks) (channels.EventModel, error) {
-	return newWASMModel(databaseName, encryption, channelsCbs)
+	uiCallbacks bindings.ChannelUICallbacks) (channels.EventModel, error) {
+	return newWASMModel(databaseName, encryption, uiCallbacks)
 }
 
 // newWASMModel creates the given [idb.Database] and returns a wasmModel.
 func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
-	channelsCbs bindings.ChannelUICallbacks) (*wasmModel, error) {
+	uiCallbacks bindings.ChannelUICallbacks) (*wasmModel, error) {
 	// Attempt to open database object
 	ctx, cancel := impl.NewContext()
 	defer cancel()
@@ -76,7 +77,14 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
 	wrapper := &wasmModel{
 		db:     db,
 		cipher: encryption,
-		cbs:    channelsCbs,
+		eventUpdate: func(eventType int64, jsonMarshallable any) {
+			data, err := json.Marshal(jsonMarshallable)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to JSON marshal %T for EventUpdate "+
+					"callback: %+v", jsonMarshallable, err)
+			}
+			uiCallbacks.EventUpdate(eventType, data)
+		},
 	}
 	return wrapper, nil
 }
diff --git a/indexedDb/worker/channels/init.go b/indexedDb/worker/channels/init.go
index 34dd3f0d2548042a3778267e9b5e46172c384dd6..dd0dd99d474299327a12aff8712ced83f9ba46e5 100644
--- a/indexedDb/worker/channels/init.go
+++ b/indexedDb/worker/channels/init.go
@@ -20,7 +20,6 @@ import (
 	"gitlab.com/elixxir/client/v4/channels"
 
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
-	"gitlab.com/elixxir/crypto/message"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
 	"gitlab.com/elixxir/xxdk-wasm/worker"
 )
@@ -28,17 +27,10 @@ import (
 // databaseSuffix is the suffix to be appended to the name of the database.
 const databaseSuffix = "_speakeasy"
 
-// MessageReceivedCallback is called any time a message is received or updated.
-//
-// update is true if the row is old and was edited.
-type MessageReceivedCallback func(uuid int64, channelID []byte, update bool)
-
-// DeletedMessageCallback is called any time a message is deleted.
-type DeletedMessageCallback func(messageID []byte)
-
-// MutedUserCallback is called any time a user is muted or unmuted. unmute is
-// true if the user has been unmuted and false if they have been muted.
-type MutedUserCallback func(channelID, pubKey []byte, unmute bool)
+// eventUpdateCallback is the [bindings.ChannelUICallback] callback function
+// it has a type ([bindings.NickNameUpdate] to [bindings.MessageDeleted]
+// and json data that is the callback information.
+type eventUpdateCallback func(eventType int64, jsonData []byte)
 
 // NewWASMEventModelBuilder returns an EventModelBuilder which allows
 // the channel manager to define the path but the callback is the same
@@ -74,15 +66,15 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher,
 
 	// Register handler to manage messages for the MessageReceivedCallback
 	wm.RegisterCallback(MessageReceivedCallbackTag,
-		messageReceivedCallbackHandler(channelCbs.MessageReceived))
+		messageReceivedCallbackHandler(channelCbs.EventUpdate))
 
 	// Register handler to manage messages for the DeletedMessageCallback
 	wm.RegisterCallback(DeletedMessageCallbackTag,
-		deletedMessageCallbackHandler(channelCbs.MessageDeleted))
+		deletedMessageCallbackHandler(channelCbs.EventUpdate))
 
 	// Register handler to manage messages for the MutedUserCallback
 	wm.RegisterCallback(MutedUserCallbackTag,
-		mutedUserCallbackHandler(channelCbs.UserMuted))
+		mutedUserCallbackHandler(channelCbs.EventUpdate))
 
 	// Store the database name
 	err = storage.StoreIndexedDb(databaseName)
@@ -129,57 +121,34 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher,
 	return &wasmModel{wm}, nil
 }
 
-// MessageReceivedCallbackMessage is JSON marshalled and received from the
-// worker for the [MessageReceivedCallback] callback.
-type MessageReceivedCallbackMessage struct {
-	UUID      int64  `json:"uuid"`
-	ChannelID []byte `json:"channelID"`
-	Update    bool   `json:"update"`
+// EventUpdateCallbackMessage is JSON marshalled and received from the worker
+// for the [EventUpdate] callback.
+type EventUpdateCallbackMessage struct {
+	EventType int64  `json:"eventType"`
+	JsonData  []byte `json:"jsonData"`
 }
 
 // messageReceivedCallbackHandler returns a handler to manage messages for the
 // MessageReceivedCallback.
-func messageReceivedCallbackHandler(cb MessageReceivedCallback) func(data []byte) {
+func messageReceivedCallbackHandler(cb eventUpdateCallback) func(data []byte) {
 	return func(data []byte) {
-		var msg MessageReceivedCallbackMessage
-		err := json.Unmarshal(data, &msg)
-		if err != nil {
-			jww.ERROR.Printf(
-				"Failed to JSON unmarshal %T from worker: %+v", msg, err)
-			return
-		}
-
-		cb(msg.UUID, msg.ChannelID, msg.Update)
+		cb(bindings.MessageReceived, data)
 	}
 }
 
 // deletedMessageCallbackHandler returns a handler to manage messages for the
 // DeletedMessageCallback.
-func deletedMessageCallbackHandler(cb DeletedMessageCallback) func(data []byte) {
+func deletedMessageCallbackHandler(cb eventUpdateCallback) func(data []byte) {
 	return func(data []byte) {
-		messageID, err := message.UnmarshalID(data)
-		if err != nil {
-			jww.ERROR.Printf(
-				"Failed to JSON unmarshal message ID from worker: %+v", err)
-		}
-
-		cb(messageID.Bytes())
+		cb(bindings.MessageDeleted, data)
 	}
 }
 
 // mutedUserCallbackHandler returns a handler to manage messages for the
 // MutedUserCallback.
-func mutedUserCallbackHandler(cb MutedUserCallback) func(data []byte) {
+func mutedUserCallbackHandler(cb eventUpdateCallback) func(data []byte) {
 	return func(data []byte) {
-		var msg MuteUserMessage
-		err := json.Unmarshal(data, &msg)
-		if err != nil {
-			jww.ERROR.Printf(
-				"Failed to JSON unmarshal %T from worker: %+v", msg, err)
-			return
-		}
-
-		cb(msg.ChannelID, msg.PubKey, msg.Unmute)
+		cb(bindings.UserMuted, data)
 	}
 }
 
diff --git a/indexedDb/worker/channels/tags.go b/indexedDb/worker/channels/tags.go
index d3555e549163c18b772cc86f965a2f7aeca7a827..019911288e4cdc2c78ad352d7813094e31aeee99 100644
--- a/indexedDb/worker/channels/tags.go
+++ b/indexedDb/worker/channels/tags.go
@@ -14,10 +14,11 @@ import "gitlab.com/elixxir/xxdk-wasm/worker"
 // List of tags that can be used when sending a message or registering a handler
 // to receive a message.
 const (
-	NewWASMEventModelTag       worker.Tag = "NewWASMEventModel"
-	MessageReceivedCallbackTag worker.Tag = "MessageReceivedCallback"
-	DeletedMessageCallbackTag  worker.Tag = "DeletedMessageCallback"
-	MutedUserCallbackTag       worker.Tag = "MutedUserCallback"
+	NewWASMEventModelTag          worker.Tag = "NewWASMEventModel"
+	NotificationUpdateCallbackTag worker.Tag = "NotificationUpdateCallback"
+	MessageReceivedCallbackTag    worker.Tag = "MessageReceivedCallback"
+	DeletedMessageCallbackTag     worker.Tag = "DeletedMessageCallback"
+	MutedUserCallbackTag          worker.Tag = "MutedUserCallback"
 
 	JoinChannelTag         worker.Tag = "JoinChannel"
 	LeaveChannelTag        worker.Tag = "LeaveChannel"
diff --git a/main.go b/main.go
index 72976b72f40ebdaa14f14bef576b2e607c3cd5bb..267bb55bb812d1950c412a51337464cd35f39b36 100644
--- a/main.go
+++ b/main.go
@@ -100,6 +100,11 @@ func setGlobals() {
 	js.Global().Set("InitializeBackup", js.FuncOf(wasm.InitializeBackup))
 	js.Global().Set("ResumeBackup", js.FuncOf(wasm.ResumeBackup))
 
+	// wasm/notifications.go
+	js.Global().Set("LoadNotifications", js.FuncOf(wasm.LoadNotifications))
+	js.Global().Set("LoadNotificationsDummy",
+		js.FuncOf(wasm.LoadNotificationsDummy))
+
 	// wasm/channels.go
 	js.Global().Set("GenerateChannelIdentity",
 		js.FuncOf(wasm.GenerateChannelIdentity))
@@ -127,10 +132,14 @@ func setGlobals() {
 	js.Global().Set("GetShareUrlType", js.FuncOf(wasm.GetShareUrlType))
 	js.Global().Set("ValidForever", js.FuncOf(wasm.ValidForever))
 	js.Global().Set("IsNicknameValid", js.FuncOf(wasm.IsNicknameValid))
+	js.Global().Set("GetNotificationReportsForMe",
+		js.FuncOf(wasm.GetNotificationReportsForMe))
 	js.Global().Set("GetNoMessageErr", js.FuncOf(wasm.GetNoMessageErr))
 	js.Global().Set("CheckNoMessageErr", js.FuncOf(wasm.CheckNoMessageErr))
 	js.Global().Set("NewChannelsDatabaseCipher",
 		js.FuncOf(wasm.NewChannelsDatabaseCipher))
+	js.Global().Set("GetNotificationReportsForMe",
+		js.FuncOf(wasm.GetNotificationReportsForMe))
 
 	// wasm/dm.go
 	js.Global().Set("InitChannelsFileTransfer",
diff --git a/storage/purge.go b/storage/purge.go
index 9cd7dd4bf69f2ce7cbb131c88b757058db758356..df75cebf9175251d53c722565e7c1111d6d9262f 100644
--- a/storage/purge.go
+++ b/storage/purge.go
@@ -51,7 +51,7 @@ func DecrementNumClientsRunning() {
 //     passed into [wasm.NewCmix].
 //
 // Returns:
-//   - Throws a TypeError if the password is incorrect or if not all cMix
+//   - Throws an error if the password is incorrect or if not all cMix
 //     followers have been stopped.
 func Purge(_ js.Value, args []js.Value) any {
 	storageDirectory := args[0].String()
diff --git a/wasm/authenticatedConnection.go b/wasm/authenticatedConnection.go
index 61087341cb62c840f4fc765930b16f193536f6d6..66162cfb68ec42906a3d596278cdf2b4801149b4 100644
--- a/wasm/authenticatedConnection.go
+++ b/wasm/authenticatedConnection.go
@@ -87,7 +87,7 @@ func (ac *AuthenticatedConnection) SendE2E(_ js.Value, args []js.Value) any {
 // resources.
 //
 // Returns:
-//   - Throws a TypeError if closing fails.
+//   - Throws an error if closing fails.
 func (ac *AuthenticatedConnection) Close(js.Value, []js.Value) any {
 	return ac.api.Close()
 }
@@ -109,7 +109,7 @@ func (ac *AuthenticatedConnection) GetPartner(js.Value, []js.Value) any {
 //     [bindings.Listener] interface.
 //
 // Returns:
-//   - Throws a TypeError is registering the listener fails.
+//   - Throws an error is registering the listener fails.
 func (ac *AuthenticatedConnection) RegisterListener(
 	_ js.Value, args []js.Value) any {
 	err := ac.api.RegisterListener(args[0].Int(),
diff --git a/wasm/backup.go b/wasm/backup.go
index d0f3a885bea45ccef74e8dca1184f644fac99ede..1a4a05de8d86410493544015501f852f72e7462d 100644
--- a/wasm/backup.go
+++ b/wasm/backup.go
@@ -70,7 +70,7 @@ func (ubf *updateBackupFunc) UpdateBackup(encryptedBackup []byte) {
 //
 // Returns:
 //   - JSON of [bindings.BackupReport] (Uint8Array).
-//   - Throws a TypeError if creating [Cmix] from backup fails.
+//   - Throws an error if creating [Cmix] from backup fails.
 func NewCmixFromBackup(_ js.Value, args []js.Value) any {
 	ndfJSON := args[0].String()
 	storageDir := args[1].String()
@@ -105,7 +105,7 @@ func NewCmixFromBackup(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - Javascript representation of the [Backup] object.
-//   - Throws a TypeError if initializing the [Backup] fails.
+//   - Throws an error if initializing the [Backup] fails.
 func InitializeBackup(_ js.Value, args []js.Value) any {
 	cb := &updateBackupFunc{utils.WrapCB(args[3], "UpdateBackup")}
 	api, err := bindings.InitializeBackup(
@@ -134,7 +134,7 @@ func InitializeBackup(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - Javascript representation of the [Backup] object.
-//   - Throws a TypeError if initializing the [Backup] fails.
+//   - Throws an error if initializing the [Backup] fails.
 func ResumeBackup(_ js.Value, args []js.Value) any {
 	cb := &updateBackupFunc{utils.WrapCB(args[2], "UpdateBackup")}
 	api, err := bindings.ResumeBackup(args[0].Int(), args[1].Int(), cb)
@@ -150,7 +150,7 @@ func ResumeBackup(_ js.Value, args []js.Value) any {
 // storage. To enable backups again, call [InitializeBackup].
 //
 // Returns:
-//   - Throws a TypeError if stopping the backup fails.
+//   - Throws an error if stopping the backup fails.
 func (b *Backup) StopBackup(js.Value, []js.Value) any {
 	err := b.api.StopBackup()
 	if err != nil {
diff --git a/wasm/channels.go b/wasm/channels.go
index fcbb4e63c57b641ba7299c3f1d481a75761f603b..a551d637a4e38512a54be92cddfc039e58fcbd4b 100644
--- a/wasm/channels.go
+++ b/wasm/channels.go
@@ -16,16 +16,11 @@ import (
 	"sync"
 	"syscall/js"
 
-	"gitlab.com/elixxir/client/v4/channels"
-	channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
-
 	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/client/v4/channels"
-	"gitlab.com/elixxir/crypto/message"
 	"gitlab.com/elixxir/wasm-utils/exception"
 	"gitlab.com/elixxir/wasm-utils/utils"
 	channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
-	"gitlab.com/xx_network/primitives/id"
 )
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -63,6 +58,7 @@ func newChannelsManagerJS(api *bindings.ChannelsManager) map[string]any {
 		"SendMessage":           js.FuncOf(cm.SendMessage),
 		"SendReply":             js.FuncOf(cm.SendReply),
 		"SendReaction":          js.FuncOf(cm.SendReaction),
+		"SendSilent":            js.FuncOf(cm.SendSilent),
 		"DeleteMessage":         js.FuncOf(cm.DeleteMessage),
 		"PinMessage":            js.FuncOf(cm.PinMessage),
 		"MuteUser":              js.FuncOf(cm.MuteUser),
@@ -82,6 +78,11 @@ func newChannelsManagerJS(api *bindings.ChannelsManager) map[string]any {
 
 		// Channel Receiving Logic and Callback Registration
 		"RegisterReceiveHandler": js.FuncOf(cm.RegisterReceiveHandler),
+
+		// Notifications
+		"SetMobileNotificationsLevel": js.FuncOf(
+			cm.SetMobileNotificationsLevel),
+		"GetNotificationLevel": js.FuncOf(cm.GetNotificationLevel),
 	}
 
 	return channelsManagerMap
@@ -108,7 +109,7 @@ func (cm *ChannelsManager) GetID(js.Value, []js.Value) any {
 //
 // Returns:
 //   - Marshalled bytes of [channel.PrivateIdentity] (Uint8Array).
-//   - Throws a TypeError if generating the identity fails.
+//   - Throws an error if generating the identity fails.
 func GenerateChannelIdentity(_ js.Value, args []js.Value) any {
 	pi, err := bindings.GenerateChannelIdentity(args[0].Int())
 	if err != nil {
@@ -131,7 +132,7 @@ var identityMap sync.Map
 //
 // Returns:
 //   - JSON of [channel.Identity] (Uint8Array).
-//   - Throws a TypeError if constructing the identity fails.
+//   - Throws an error if constructing the identity fails.
 func ConstructIdentity(_ js.Value, args []js.Value) any {
 	// Note: This function is similar to constructIdentity below except that it
 	//  uses a sync.Map backend to increase efficiency for identities that were
@@ -165,7 +166,7 @@ func ConstructIdentity(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of [channel.Identity] (Uint8Array).
-//   - Throws a TypeError if constructing the identity fails.
+//   - Throws an error if constructing the identity fails.
 func constructIdentity(_ js.Value, args []js.Value) any {
 	identity, err := bindings.ConstructIdentity(
 		utils.CopyBytesToGo(args[0]), args[1].Int())
@@ -187,7 +188,7 @@ func constructIdentity(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of [channel.PrivateIdentity] (Uint8Array).
-//   - Throws a TypeError if importing the identity fails.
+//   - Throws an error if importing the identity fails.
 func ImportPrivateIdentity(_ js.Value, args []js.Value) any {
 	password := args[0].String()
 	data := utils.CopyBytesToGo(args[1])
@@ -209,7 +210,7 @@ func ImportPrivateIdentity(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of the constructed [channel.Identity] (Uint8Array).
-//   - Throws a TypeError if unmarshalling the bytes or marshalling the identity
+//   - Throws an error if unmarshalling the bytes or marshalling the identity
 //     fails.
 func GetPublicChannelIdentity(_ js.Value, args []js.Value) any {
 	marshaledPublic := utils.CopyBytesToGo(args[0])
@@ -232,7 +233,7 @@ func GetPublicChannelIdentity(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of the public identity ([channel.Identity]) (Uint8Array).
-//   - Throws a TypeError if unmarshalling the bytes or marshalling the identity
+//   - Throws an error if unmarshalling the bytes or marshalling the identity
 //     fails.
 func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) any {
 	marshaledPrivate := utils.CopyBytesToGo(args[0])
@@ -257,27 +258,34 @@ func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) any {
 // Parameters:
 //   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
 //     using [Cmix.GetID].
-//   - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
+//   - args[1] - ID of [bindings.Notifications] object in tracker (int). Can be
+//     retrieved using [Notifications.GetID].
+//   - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is
 //     generated by [GenerateChannelIdentity] (Uint8Array).
-//   - args[2] - JSON of an array of integers of [channels.ExtensionBuilder]
+//   - args[3] - JSON of an array of integers of [channels.ExtensionBuilder]
 //     IDs. The ID can be retrieved from an object with an extension builder
 //     (e.g., [ChannelsFileTransfer.GetExtensionBuilderID]). Leave empty if not
 //     using extension builders. Example: `[2,11,5]` (Uint8Array).
-//   - args[3] - A function that initialises and returns a Javascript object
+//   - args[4] - A function that initialises and returns a Javascript object
 //     that matches the [bindings.EventModel] interface. The function must match
 //     the Build function in [bindings.EventModelBuilder].
+//   - args[4] - A callback object which implements the
+//     [bindings.ChannelUICallbacks] javascript functions.
 //
 // Returns:
 //   - Javascript representation of the [ChannelsManager] object.
-//   - Throws a TypeError if creating the manager fails.
+//   - Throws an error if creating the manager fails.
 func NewChannelsManager(_ js.Value, args []js.Value) any {
 	cmixId := args[0].Int()
-	privateIdentity := utils.CopyBytesToGo(args[1])
-	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[2])
-	em := newEventModelBuilder(args[3])
+	notificationsId := args[1].Int()
+	privateIdentity := utils.CopyBytesToGo(args[2])
+	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[3])
+	em := newEventModelBuilder(args[4])
+	channelCbs := newChannelUI(args[5])
 
 	cm, err := bindings.NewChannelsManager(
-		cmixId, privateIdentity, extensionBuilderIDsJSON, em, nil)
+		cmixId, privateIdentity, extensionBuilderIDsJSON, em,
+		notificationsId, channelCbs)
 	if err != nil {
 		exception.ThrowTrace(err)
 		return nil
@@ -297,22 +305,27 @@ func NewChannelsManager(_ js.Value, args []js.Value) any {
 // Parameters:
 //   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
 //     using [Cmix.GetID].
-//   - args[1] - The storage tag associated with the previously created channel
+//   - args[1] - ID of [bindings.Notifications] object in tracker (int). Can be
+//     retrieved using [Notifications.GetID].
+//   - args[2] - The storage tag associated with the previously created channel
 //     manager and retrieved with [ChannelsManager.GetStorageTag] (string).
-//   - args[2] - A function that initialises and returns a Javascript object
+//   - args[3] - A function that initialises and returns a Javascript object
 //     that matches the [bindings.EventModel] interface. The function must match
 //     the Build function in [bindings.EventModelBuilder].
-//   - args[3] - A callback object which implements the
+//   - args[4] - A callback object which implements the
 //     [bindings.ChannelUICallbacks] javascript functions.
 //
 // Returns:
 //   - Javascript representation of the [ChannelsManager] object.
-//   - Throws a TypeError if loading the manager fails.
+//   - Throws an error if loading the manager fails.
 func LoadChannelsManager(_ js.Value, args []js.Value) any {
-	em := newEventModelBuilder(args[2])
-	cUI := newChannelUI(args[3])
-	cm, err := bindings.LoadChannelsManager(args[0].Int(), args[1].String(),
-		em, cUI)
+	cMixID := args[0].Int()
+	notificationsID := args[1].Int()
+	storageTag := args[2].String()
+	em := newEventModelBuilder(args[3])
+	cUI := newChannelUI(args[4])
+	cm, err := bindings.LoadChannelsManager(cMixID, storageTag, em,
+		notificationsID, cUI)
 	if err != nil {
 		exception.ThrowTrace(err)
 		return nil
@@ -335,37 +348,41 @@ func LoadChannelsManager(_ js.Value, args []js.Value) any {
 // Parameters:
 //   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
 //     using [Cmix.GetID].
-//   - args[1] - Path to Javascript file that starts the worker (string).
-//   - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is
+//   - args[1] - ID of [bindings.Notifications] object in tracker (int). Can be
+//     retrieved using [Notifications.GetID].
+//   - args[2] - Path to Javascript file that starts the worker (string).
+//   - args[3] - Bytes of a private identity ([channel.PrivateIdentity]) that is
 //     generated by [GenerateChannelIdentity] (Uint8Array).
-//   - args[3] - JSON of an array of integers of [channels.ExtensionBuilder]
+//   - args[4] - JSON of an array of integers of [channels.ExtensionBuilder]
 //     IDs. The ID can be retrieved from an object with an extension builder
 //     (e.g., [ChannelsFileTransfer.GetExtensionBuilderID]). Leave empty if not
 //     using extension builders. Example: `[2,11,5]` (Uint8Array).
-//   - args[4] - A callback object which implements the
+//   - args[5] - A callback object which implements the
 //     [bindings.ChannelUICallbacks] javascript functions.
-//   - args[5] - ID of [ChannelDbCipher] object in tracker (int). Create this
+//   - args[6] - ID of [ChannelDbCipher] object in tracker (int). Create this
 //     object with [NewChannelsDatabaseCipher] and get its id with
 //     [ChannelDbCipher.GetID].
 //
 // Returns a promise:
 //   - Resolves to a Javascript representation of the [ChannelsManager] object.
 //   - Rejected with an error if loading indexedDb or the manager fails.
-//   - Throws a TypeError if the cipher ID does not correspond to a cipher.
+//   - Throws an error if the cipher ID does not correspond to a cipher.
 func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
-	wasmJsPath := args[1].String()
-	privateIdentity := utils.CopyBytesToGo(args[2])
-	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[3])
-	channelCbs := newChannelUI(args[4])
-	cipherID := args[5].Int()
+	notificationsID := args[1].Int()
+	wasmJsPath := args[2].String()
+	privateIdentity := utils.CopyBytesToGo(args[3])
+	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[4])
+	channelCbs := newChannelUI(args[5])
+	cipherID := args[6].Int()
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
 		exception.ThrowTrace(err)
 	}
 
-	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
+	return newChannelsManagerWithIndexedDb(cmixID, notificationsID,
+		wasmJsPath, privateIdentity,
 		extensionBuilderIDsJSON, channelCbs, cipher)
 }
 
@@ -384,14 +401,16 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 // Parameters:
 //   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
 //     using [Cmix.GetID].
-//   - args[1] - Path to Javascript file that starts the worker (string).
-//   - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is
+//   - args[1] - ID of [bindings.Notifications] object in tracker (int). Can be
+//     retrieved using [Notifications.GetID].
+//   - args[2] - Path to Javascript file that starts the worker (string).
+//   - args[3] - Bytes of a private identity ([channel.PrivateIdentity]) that is
 //     generated by [GenerateChannelIdentity] (Uint8Array).
-//   - args[3] - JSON of an array of integers of [channels.ExtensionBuilder]
+//   - args[4] - JSON of an array of integers of [channels.ExtensionBuilder]
 //     IDs. The ID can be retrieved from an object with an extension builder
 //     (e.g., [ChannelsFileTransfer.GetExtensionBuilderID]). Leave empty if not
 //     using extension builders. Example: `[2,11,5]` (Uint8Array).
-//   - args[4] - A callback object which implements the
+//   - args[5] - A callback object which implements the
 //     [bindings.ChannelUICallbacks] javascript functions.
 //
 // Returns a promise:
@@ -401,17 +420,19 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 // FIXME: package names in comments for indexedDb
 func NewChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
-	wasmJsPath := args[1].String()
-	privateIdentity := utils.CopyBytesToGo(args[2])
-	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[3])
-	channelsCbs := newChannelUI(args[4])
-
-	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
+	notificationsID := args[1].Int()
+	wasmJsPath := args[2].String()
+	privateIdentity := utils.CopyBytesToGo(args[3])
+	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[4])
+	channelsCbs := newChannelUI(args[5])
+
+	return newChannelsManagerWithIndexedDb(cmixID, notificationsID,
+		wasmJsPath, privateIdentity,
 		extensionBuilderIDsJSON, channelsCbs, nil)
 }
 
-func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
-	privateIdentity, extensionBuilderIDsJSON []byte,
+func newChannelsManagerWithIndexedDb(cmixID, notificationsID int,
+	wasmJsPath string, privateIdentity, extensionBuilderIDsJSON []byte,
 	channelsCbs bindings.ChannelUICallbacks,
 	cipher *bindings.ChannelDbCipher) any {
 
@@ -420,7 +441,8 @@ func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.NewChannelsManagerGoEventModel(
-			cmixID, privateIdentity, extensionBuilderIDsJSON, model, channelsCbs)
+			cmixID, privateIdentity, extensionBuilderIDsJSON, model,
+			notificationsID, channelsCbs)
 		if err != nil {
 			reject(exception.NewTrace(err))
 		} else {
@@ -442,32 +464,36 @@ func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
 // Parameters:
 //   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
 //     using [Cmix.GetID].
-//   - args[1] - Path to Javascript file that starts the worker (string).
-//   - args[2] - The storage tag associated with the previously created channel
+//   - args[1] - ID of [bindings.Notifications] object in tracker (int). Can be
+//     retrieved using [Notifications.GetID].
+//   - args[2] - Path to Javascript file that starts the worker (string).
+//   - args[3] - The storage tag associated with the previously created channel
 //     manager and retrieved with [ChannelsManager.GetStorageTag] (string).
-//   - args[3] - A callback object which implements the
+//   - args[4] - A callback object which implements the
 //     [bindings.ChannelUICallbacks] javascript functions.
-//   - args[4] - ID of [ChannelDbCipher] object in tracker (int). Create this
+//   - args[5] - ID of [ChannelDbCipher] object in tracker (int). Create this
 //     object with [NewChannelsDatabaseCipher] and get its id with
 //     [ChannelDbCipher.GetID].
 //
 // Returns a promise:
 //   - Resolves to a Javascript representation of the [ChannelsManager] object.
 //   - Rejected with an error if loading indexedDb or the manager fails.
-//   - Throws a TypeError if the cipher ID does not correspond to a cipher.
+//   - Throws an error if the cipher ID does not correspond to a cipher.
 func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
-	wasmJsPath := args[1].String()
-	storageTag := args[2].String()
-	channelsCbs := newChannelUI(args[3])
-	cipherID := args[4].Int()
+	notificationsID := args[1].Int()
+	wasmJsPath := args[2].String()
+	storageTag := args[3].String()
+	channelsCbs := newChannelUI(args[4])
+	cipherID := args[5].Int()
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
 		exception.ThrowTrace(err)
 	}
 
-	return loadChannelsManagerWithIndexedDb(cmixID, wasmJsPath, storageTag,
+	return loadChannelsManagerWithIndexedDb(cmixID, notificationsID,
+		wasmJsPath, storageTag,
 		channelsCbs, cipher)
 }
 
@@ -484,10 +510,12 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 // Parameters:
 //   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
 //     using [Cmix.GetID].
-//   - args[1] - Path to Javascript file that starts the worker (string).
-//   - args[2] - The storage tag associated with the previously created channel
+//   - args[1] - ID of [bindings.Notifications] object in tracker (int). Can be
+//     retrieved using [Notifications.GetID].
+//   - args[2] - Path to Javascript file that starts the worker (string).
+//   - args[3] - The storage tag associated with the previously created channel
 //     manager and retrieved with [ChannelsManager.GetStorageTag] (string).
-//   - args[3] - A callback object which implements the
+//   - args[4] - A callback object which implements the
 //     [bindings.ChannelUICallbacks] javascript functions.
 //
 // Returns a promise:
@@ -495,15 +523,18 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 //   - Rejected with an error if loading indexedDb or the manager fails.
 func LoadChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
-	wasmJsPath := args[1].String()
-	storageTag := args[2].String()
-	cUI := newChannelUI(args[3])
+	notificationsID := args[1].Int()
+	wasmJsPath := args[2].String()
+	storageTag := args[3].String()
+	cUI := newChannelUI(args[4])
 
-	return loadChannelsManagerWithIndexedDb(cmixID, wasmJsPath, storageTag,
+	return loadChannelsManagerWithIndexedDb(cmixID, notificationsID,
+		wasmJsPath, storageTag,
 		cUI, nil)
 }
 
-func loadChannelsManagerWithIndexedDb(cmixID int, wasmJsPath, storageTag string,
+func loadChannelsManagerWithIndexedDb(cmixID, notificationsID int,
+	wasmJsPath, storageTag string,
 	channelsCbs bindings.ChannelUICallbacks,
 	cipher *bindings.ChannelDbCipher) any {
 
@@ -512,7 +543,8 @@ func loadChannelsManagerWithIndexedDb(cmixID int, wasmJsPath, storageTag string,
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.LoadChannelsManagerGoEventModel(
-			cmixID, storageTag, model, nil, channelsCbs)
+			cmixID, storageTag, model, nil, notificationsID,
+			channelsCbs)
 		if err != nil {
 			reject(exception.NewTrace(err))
 		} else {
@@ -612,7 +644,7 @@ func GetChannelJSON(_ js.Value, args []js.Value) any {
 // Returns:
 //   - JSON of [bindings.ChannelInfo], which describes all relevant channel info
 //     (Uint8Array).
-//   - Throws a TypeError if getting the channel info fails.
+//   - Throws an error if getting the channel info fails.
 func GetChannelInfo(_ js.Value, args []js.Value) any {
 	ci, err := bindings.GetChannelInfo(args[0].String())
 	if err != nil {
@@ -737,7 +769,7 @@ func (cm *ChannelsManager) LeaveChannel(_ js.Value, args []js.Value) any {
 //   - args[0] - Marshalled bytes of the channel's [id.ID] (Uint8Array).
 //
 // Returns:
-//   - Throws a TypeError if the replay fails.
+//   - Throws an error if the replay fails.
 func (cm *ChannelsManager) ReplayChannel(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 
@@ -754,7 +786,7 @@ func (cm *ChannelsManager) ReplayChannel(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of an array of marshalled [id.ID] (Uint8Array).
-//   - Throws a TypeError if getting the channels fails.
+//   - Throws an error if getting the channels fails.
 //
 // JSON Example:
 //
@@ -779,7 +811,7 @@ func (cm *ChannelsManager) GetChannels(js.Value, []js.Value) any {
 //   - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
 //
 // Returns:
-//   - Throws a TypeError if saving the DM token fails.
+//   - Throws an error if saving the DM token fails.
 func (cm *ChannelsManager) EnableDirectMessages(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 	err := cm.api.EnableDirectMessages(marshalledChanId)
@@ -797,7 +829,7 @@ func (cm *ChannelsManager) EnableDirectMessages(_ js.Value, args []js.Value) any
 //   - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
 //
 // Returns:
-//   - Throws a TypeError if saving the DM token fails
+//   - Throws an error if saving the DM token fails
 func (cm *ChannelsManager) DisableDirectMessages(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 	err := cm.api.DisableDirectMessages(marshalledChanId)
@@ -815,7 +847,7 @@ func (cm *ChannelsManager) DisableDirectMessages(_ js.Value, args []js.Value) an
 //
 // Returns:
 //   - enabled (bool) - status of dms for passed in channel ID, true if enabled
-//   - Throws a TypeError if unmarshalling the channel ID
+//   - Throws an error if unmarshalling the channel ID
 func (cm *ChannelsManager) AreDMsEnabled(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 	enabled, err := cm.api.AreDMsEnabled(marshalledChanId)
@@ -863,7 +895,7 @@ type ShareURL struct {
 //
 // Returns:
 //   - JSON of [bindings.ShareURL] (Uint8Array).
-//   - Throws a TypeError if generating the URL fails.
+//   - Throws an error if generating the URL fails.
 func (cm *ChannelsManager) GetShareURL(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
 	host := args[1].String()
@@ -888,7 +920,7 @@ func (cm *ChannelsManager) GetShareURL(_ js.Value, args []js.Value) any {
 // Returns:
 //   - An int that corresponds to the [broadcast.PrivacyLevel] as outlined
 //     below (int).
-//   - Throws a TypeError if parsing the URL fails.
+//   - Throws an error if parsing the URL fails.
 //
 // Possible returns:
 //
@@ -942,6 +974,9 @@ func ValidForever(js.Value, []js.Value) any {
 //     to the user should be tracked while all actions should not be (boolean).
 //   - args[5] - JSON of [xxdk.CMIXParams]. If left empty
 //     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
+//   - args[6] - pingBytes - An array of public keys of users that
+//     should receive mobile notifications for the message (JSON of
+//     []Uint8Array list).
 //
 // Returns a promise:
 //   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
@@ -953,10 +988,16 @@ func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any {
 	leaseTimeMS := int64(args[3].Int())
 	tracked := args[4].Bool()
 	cmixParamsJSON := utils.CopyBytesToGo(args[5])
+	var pingBytes [][]byte
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
-		sendReport, err := cm.api.SendGeneric(marshalledChanId, messageType,
-			msg, leaseTimeMS, tracked, cmixParamsJSON)
+		err := json.Unmarshal(utils.CopyBytesToGo(args[6]), pingBytes)
+		if err != nil {
+			reject(exception.NewTrace(err))
+		}
+		sendReport, err := cm.api.SendGeneric(marshalledChanId,
+			messageType, msg, leaseTimeMS, tracked,
+			cmixParamsJSON, pingBytes)
 		if err != nil {
 			reject(exception.NewTrace(err))
 		} else {
@@ -986,6 +1027,9 @@ func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any {
 //     be enumerated here. Use [ValidForever] to last the max message life.
 //   - args[3] - JSON of [xxdk.CMIXParams]. If left empty
 //     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
+//   - args[4] - pingBytes - An array of public keys of users that
+//     should receive mobile notifications for the message (JSON of
+//     []Uint8Array list).
 //
 // Returns a promise:
 //   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
@@ -995,10 +1039,16 @@ func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any {
 	msg := args[1].String()
 	leaseTimeMS := int64(args[2].Int())
 	cmixParamsJSON := utils.CopyBytesToGo(args[3])
+	var pingBytes [][]byte
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
+		err := json.Unmarshal(utils.CopyBytesToGo(args[6]), pingBytes)
+		if err != nil {
+			reject(exception.NewTrace(err))
+		}
 		sendReport, err := cm.api.SendMessage(
-			marshalledChanId, msg, leaseTimeMS, cmixParamsJSON)
+			marshalledChanId, msg, leaseTimeMS, cmixParamsJSON,
+			pingBytes)
 		if err != nil {
 			reject(exception.NewTrace(err))
 		} else {
@@ -1035,6 +1085,9 @@ func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any {
 //     be enumerated here. Use [ValidForever] to last the max message life.
 //   - args[4] - JSON of [xxdk.CMIXParams]. If left empty
 //     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
+//   - args[6] - pingBytes - An array of public keys of users that
+//     should receive mobile notifications for the message (JSON of
+//     []Uint8Array list).
 //
 // Returns a promise:
 //   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
@@ -1045,10 +1098,16 @@ func (cm *ChannelsManager) SendReply(_ js.Value, args []js.Value) any {
 	messageToReactTo := utils.CopyBytesToGo(args[2])
 	leaseTimeMS := int64(args[3].Int())
 	cmixParamsJSON := utils.CopyBytesToGo(args[4])
+	var pingBytes [][]byte
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
+		err := json.Unmarshal(utils.CopyBytesToGo(args[6]), pingBytes)
+		if err != nil {
+			reject(exception.NewTrace(err))
+		}
 		sendReport, err := cm.api.SendReply(marshalledChanId, msg,
-			messageToReactTo, leaseTimeMS, cmixParamsJSON)
+			messageToReactTo, leaseTimeMS, cmixParamsJSON,
+			pingBytes)
 		if err != nil {
 			reject(exception.NewTrace(err))
 		} else {
@@ -1081,22 +1140,67 @@ func (cm *ChannelsManager) SendReply(_ js.Value, args []js.Value) any {
 //     be enumerated here. Use [ValidForever] to last the max message life.
 //   - args[4] - JSON of [xxdk.CMIXParams]. If left empty
 //     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
+//   - args[5] - pingBytes - An array of public keys of users that
+//     should receive mobile notifications for the message (JSON of
+//     []Uint8Array list).
 //
 // Returns a promise:
 //   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
 //   - Rejected with an error if sending fails.
 func (cm *ChannelsManager) SendReaction(_ js.Value, args []js.Value) any {
+	marshalledChanId := utils.CopyBytesToGo(args[0])
+	reaction := args[1].String()
+	messageToReactTo := utils.CopyBytesToGo(args[2])
+	leaseTimeMS := int64(args[3].Int())
+	cmixParamsJSON := utils.CopyBytesToGo(args[4])
+	var pingBytes [][]byte
+
+	promiseFn := func(resolve, reject func(args ...any) js.Value) {
+		err := json.Unmarshal(utils.CopyBytesToGo(args[6]), pingBytes)
+		if err != nil {
+			reject(exception.NewTrace(err))
+		}
+		sendReport, err := cm.api.SendReaction(marshalledChanId, reaction,
+			messageToReactTo, leaseTimeMS, cmixParamsJSON)
+		if err != nil {
+			reject(exception.NewTrace(err))
+		} else {
+			resolve(utils.CopyBytesToJS(sendReport))
+		}
+	}
+
+	return utils.CreatePromise(promiseFn)
+}
+
+// SendSilent is used to send to a channel a message with no notifications.
+// Its primary purpose is to communicate new nicknames without calling
+// [SendMessage].
+//
+// It takes no payload intentionally as the message should be very lightweight.
+//
+// Parameters:
+//   - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
+//   - args[1] - The lease of the message. This will be how long the
+//     message is available from the network, in milliseconds (int). 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. Use [ValidForever] to last the max message life.
+//   - args[2] - JSON of [xxdk.CMIXParams]. If left empty
+//     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
+//
+// Returns a promise:
+//   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
+//   - Rejected with an error if sending fails.
+func (cm *ChannelsManager) SendSilent(_ js.Value, args []js.Value) any {
 	var (
 		marshalledChanId = utils.CopyBytesToGo(args[0])
-		reaction         = args[1].String()
-		messageToReactTo = utils.CopyBytesToGo(args[2])
-		leaseTimeMS      = int64(args[3].Int())
-		cmixParamsJSON   = utils.CopyBytesToGo(args[4])
+		leaseTimeMS      = int64(args[1].Int())
+		cmixParamsJSON   = utils.CopyBytesToGo(args[2])
 	)
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
-		sendReport, err := cm.api.SendReaction(marshalledChanId, reaction,
-			messageToReactTo, leaseTimeMS, cmixParamsJSON)
+		sendReport, err := cm.api.SendSilent(
+			marshalledChanId, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
 			reject(exception.NewTrace(err))
 		} else {
@@ -1420,7 +1524,7 @@ func IsNicknameValid(_ js.Value, args []js.Value) any {
 // Returns:
 //   - Returns true if the user is muted in the channel and false otherwise
 //     (boolean).
-//   - Throws a TypeError if the channel ID cannot be unmarshalled.
+//   - Throws an error if the channel ID cannot be unmarshalled.
 func (cm *ChannelsManager) Muted(_ js.Value, args []js.Value) any {
 	channelIDBytes := utils.CopyBytesToGo(args[0])
 
@@ -1443,7 +1547,7 @@ func (cm *ChannelsManager) Muted(_ js.Value, args []js.Value) any {
 // Returns:
 //   - JSON of an array of ed25519.PublicKey (Uint8Array). Look below for an
 //     example.
-//   - Throws a TypeError if the channel ID cannot be unmarshalled.
+//   - Throws an error if the channel ID cannot be unmarshalled.
 //
 // Example return:
 //
@@ -1459,6 +1563,88 @@ func (cm *ChannelsManager) GetMutedUsers(_ js.Value, args []js.Value) any {
 	return utils.CopyBytesToJS(mutedUsers)
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Notifications                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+// GetNotificationLevel implements
+// [bindings.ChannelsManager.GetNotificationLevel]
+//
+// Parameters:
+//   - args[0] - channelIDBytes - The marshalled bytes of the
+//     channel's [id.ID] (Uint8Array)
+//
+// Returns:
+//   - int - The [channels.NotificationLevel] for the channel.
+//     throws an error if there is an issue
+func (cm *ChannelsManager) GetNotificationLevel(_ js.Value,
+	args []js.Value) any {
+	channelIDBytes := utils.CopyBytesToGo(args[0])
+
+	level, err := cm.api.GetNotificationLevel(channelIDBytes)
+	if err != nil {
+		exception.ThrowTrace(err)
+		return nil
+	}
+
+	return level
+}
+
+// SetMobileNotificationsLevel implements
+// [bindings.ChannelsManager.SetMobileNotificationsLevel]
+//
+// Parameters:
+//   - args[0] - channelIDBytes - The marshaled bytes of the channel's [id.ID]
+//     (Uint8Array).
+//   - args[1] - level - The [channels.NotificationLevel] to set for
+//     the channel.
+//   - args[2] - status - The [notifications.NotificationState] to set
+//     for the channel.
+//   - args[3] - push - True to enable push notifications and false to
+//     only have in-app notifications.
+//
+// Returns nothing or throws an error
+func (cm *ChannelsManager) SetMobileNotificationsLevel(_ js.Value,
+	args []js.Value) any {
+	channelIDBytes := utils.CopyBytesToGo(args[0])
+	level := args[1].Int()
+	status := args[2].Int()
+	push := args[3].Bool()
+
+	err := cm.api.SetMobileNotificationsLevel(channelIDBytes, level,
+		status, push)
+	if err != nil {
+		exception.ThrowTrace(err)
+	}
+	return nil
+}
+
+// GetNotificationReportsForMe implements
+// [bindings.GetNotificationsReportsForMe]
+//
+// Parameters:
+//   - args[0] - notificationFilterJSON - JSON of a slice of
+//     [channels.NotificationFilter] (Uint8Array).
+//   - args[1] - notificationDataJSON - JSON of a slice of
+//     [notifications.Data] (Uint8Array).
+//
+// Returns:
+//   - []byte - JSON of a slice of [channels.NotificationReport].
+//     Throws an error if one occurs
+func GetNotificationReportsForMe(_ js.Value, args []js.Value) any {
+	notificationFilterJSON := utils.CopyBytesToGo(args[0])
+	notificationDataJSON := utils.CopyBytesToGo(args[1])
+
+	nrs, err := bindings.GetNotificationReportsForMe(notificationFilterJSON,
+		notificationDataJSON)
+	if err != nil {
+		exception.ThrowTrace(err)
+		return nil
+	}
+
+	return utils.CopyBytesToJS(nrs)
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Admin Management                                                           //
 ////////////////////////////////////////////////////////////////////////////////
@@ -1471,7 +1657,7 @@ func (cm *ChannelsManager) GetMutedUsers(_ js.Value, args []js.Value) any {
 // Returns:
 //   - True if the user is an admin in the channel and false otherwise
 //     (boolean).
-//   - Throws a TypeError if the channel ID cannot be unmarshalled.
+//   - Throws an error if the channel ID cannot be unmarshalled.
 func (cm *ChannelsManager) IsChannelAdmin(_ js.Value, args []js.Value) any {
 	isAdmin, err := cm.api.IsChannelAdmin(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -1507,7 +1693,7 @@ func (cm *ChannelsManager) IsChannelAdmin(_ js.Value, args []js.Value) any {
 // Returns:
 //   - Portable string of the channel private key encrypted with the password
 //     (Uint8Array).
-//   - Throws a TypeError if the user is not an admin for the channel.
+//   - Throws an error if the user is not an admin for the channel.
 func (cm *ChannelsManager) ExportChannelAdminKey(_ js.Value, args []js.Value) any {
 	pk, err := cm.api.ExportChannelAdminKey(
 		utils.CopyBytesToGo(args[0]), args[1].String())
@@ -1530,14 +1716,14 @@ func (cm *ChannelsManager) ExportChannelAdminKey(_ js.Value, args []js.Value) an
 // Returns:
 //   - Returns false if private key does not belong to the given channel ID
 //     (boolean).
-//   - Throws a TypeError if the password is invalid.
+//   - Throws an error if the password is invalid.
 //
 // Returns:
 //   - bool - True if the private key belongs to the channel and false
 //     otherwise.
-//   - Throws a TypeError with the message [channels.WrongPasswordErr] for an
+//   - Throws an error with the message [channels.WrongPasswordErr] for an
 //     invalid password.
-//   - Throws a TypeError with the message [channels.ChannelDoesNotExistsErr] i
+//   - Throws an error with the message [channels.ChannelDoesNotExistsErr] i
 //     the channel has not already been joined.
 func (cm *ChannelsManager) VerifyChannelAdminKey(_ js.Value, args []js.Value) any {
 	channelID := utils.CopyBytesToGo(args[0])
@@ -1564,13 +1750,13 @@ func (cm *ChannelsManager) VerifyChannelAdminKey(_ js.Value, args []js.Value) an
 //   - args[2] - The encrypted channel private key packet (Uint8Array).
 //
 // Returns:
-//   - Throws a TypeError if the password is invalid or the private key does
+//   - Throws an error if the password is invalid or the private key does
 //     not match the channel ID.
-//   - Throws a TypeError with the message [channels.WrongPasswordErr] for an
+//   - Throws an error with the message [channels.WrongPasswordErr] for an
 //     invalid password.
-//   - Throws a TypeError with the message [channels.ChannelDoesNotExistsErr] if
+//   - Throws an error with the message [channels.ChannelDoesNotExistsErr] if
 //     the channel has not already been joined.
-//   - Throws a TypeError with the message [channels.WrongPrivateKeyErr] if the
+//   - Throws an error with the message [channels.WrongPrivateKeyErr] if the
 //     private key does not belong to the channel.
 func (cm *ChannelsManager) ImportChannelAdminKey(_ js.Value, args []js.Value) any {
 	channelID := utils.CopyBytesToGo(args[0])
@@ -1596,7 +1782,7 @@ func (cm *ChannelsManager) ImportChannelAdminKey(_ js.Value, args []js.Value) an
 //   - args[0] - The marshalled bytes of the channel's [id.ID] (Uint8Array)
 //
 // Returns:
-//   - Throws a TypeError if the deletion fails.
+//   - Throws an error if the deletion fails.
 func (cm *ChannelsManager) DeleteChannelAdminKey(_ js.Value, args []js.Value) any {
 	err := cm.api.DeleteChannelAdminKey(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -1658,7 +1844,7 @@ func (cmrCB *channelMessageReceptionCallback) Callback(
 //     users (boolean).
 //
 // Returns:
-//   - Throws a TypeError if registering the handler fails.
+//   - Throws an error if registering the handler fails.
 func (cm *ChannelsManager) RegisterReceiveHandler(_ js.Value, args []js.Value) any {
 	messageType := args[0].Int()
 	listenerCb := &channelMessageReceptionCallback{
@@ -2089,7 +2275,7 @@ func newChannelDbCipherJS(api *bindings.ChannelDbCipher) map[string]any {
 //
 // Returns:
 //   - JavaScript representation of the [ChannelDbCipher] object.
-//   - Throws a TypeError if creating the cipher fails.
+//   - Throws an error if creating the cipher fails.
 func NewChannelsDatabaseCipher(_ js.Value, args []js.Value) any {
 	cmixId := args[0].Int()
 	password := utils.CopyBytesToGo(args[1])
@@ -2124,7 +2310,7 @@ func (c *ChannelDbCipher) GetID(js.Value, []js.Value) any {
 //
 // Returns:
 //   - The ciphertext of the plaintext passed in (Uint8Array).
-//   - Throws a TypeError if it fails to encrypt the plaintext.
+//   - Throws an error if it fails to encrypt the plaintext.
 func (c *ChannelDbCipher) Encrypt(_ js.Value, args []js.Value) any {
 	ciphertext, err := c.api.Encrypt(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -2145,7 +2331,7 @@ func (c *ChannelDbCipher) Encrypt(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - The plaintext of the ciphertext passed in (Uint8Array).
-//   - Throws a TypeError if it fails to encrypt the plaintext.
+//   - Throws an error if it fails to encrypt the plaintext.
 func (c *ChannelDbCipher) Decrypt(_ js.Value, args []js.Value) any {
 	plaintext, err := c.api.Decrypt(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -2160,7 +2346,7 @@ func (c *ChannelDbCipher) Decrypt(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of the cipher (Uint8Array).
-//   - Throws a TypeError if marshalling fails.
+//   - Throws an error if marshalling fails.
 func (c *ChannelDbCipher) MarshalJSON(js.Value, []js.Value) any {
 	data, err := c.api.MarshalJSON()
 	if err != nil {
@@ -2181,7 +2367,7 @@ func (c *ChannelDbCipher) MarshalJSON(js.Value, []js.Value) any {
 //
 // Returns:
 //   - JSON of the cipher (Uint8Array).
-//   - Throws a TypeError if marshalling fails.
+//   - Throws an error if marshalling fails.
 func (c *ChannelDbCipher) UnmarshalJSON(_ js.Value, args []js.Value) any {
 	err := c.api.UnmarshalJSON(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -2195,40 +2381,18 @@ func (c *ChannelDbCipher) UnmarshalJSON(_ js.Value, args []js.Value) any {
 // channelUI callbacks implementation struct.
 func newChannelUI(cbImpl js.Value) *channelUI {
 	return &channelUI{
-		messageReceived: utils.WrapCB(cbImpl, "MessageReceived"),
-		userMuted:       utils.WrapCB(cbImpl, "UserMuted"),
-		messageDeleted:  utils.WrapCB(cbImpl, "MessageDeleted"),
-		nicknameUpdate:  utils.WrapCB(cbImpl, "NicknameUpdate"),
+		eventUpdate: utils.WrapCB(cbImpl, "EventUpdate"),
 	}
 }
 
 // eventModel wraps Javascript callbacks to adhere to the
 // [bindings.ChannelUICallbacks] interface.
 type channelUI struct {
-	messageReceived func(args ...any) js.Value
-	userMuted       func(args ...any) js.Value
-	messageDeleted  func(args ...any) js.Value
-	nicknameUpdate  func(args ...any) js.Value
-}
-
-// MessageReceived implements [bindings.ChannelUICallbacks.MessageReceived].
-func (c *channelUI) MessageReceived(uuid int64, channelID []byte, update bool) {
-	c.messageReceived(uuid, utils.CopyBytesToJS(channelID), update)
-}
-
-// UserMuted implements [bindings.ChannelUICallbacks.UserMuted].
-func (c *channelUI) UserMuted(channelID []byte, pubKey []byte, unmute bool) {
-	c.userMuted(utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(pubKey),
-		unmute)
-}
-
-// MessageDeleted implements [bindings.ChannelUICallbacks.MessageDeleted].
-func (c *channelUI) MessageDeleted(messageId []byte) {
-	c.messageDeleted(utils.CopyBytesToJS(messageId))
+	eventUpdate func(args ...any) js.Value
 }
 
-// NicknameUpdate implements [bindings.ChannelUICallbacks.NicknameUpdate]
-func (c *channelUI) NicknameUpdate(channelIdBytes []byte, nickname string,
-	exists bool) {
-	c.nicknameUpdate(utils.CopyBytesToJS(channelIdBytes), nickname, exists)
+// EventUpdate implements
+// [bindings.ChannelUICallbacks.EventUpdate].
+func (c *channelUI) EventUpdate(eventType int64, dataJson []byte) {
+	c.eventUpdate(eventType, utils.CopyBytesToJS(dataJson))
 }
diff --git a/wasm/cmix.go b/wasm/cmix.go
index 3b735f4eb477652b80ed9b54b93c5334cfe33c6d..be7ad6d31f2052b6f9750703611b4789cff35009 100644
--- a/wasm/cmix.go
+++ b/wasm/cmix.go
@@ -15,7 +15,6 @@ import (
 	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/wasm-utils/exception"
 	"gitlab.com/elixxir/wasm-utils/utils"
-	"syscall/js"
 )
 
 // Cmix wraps the [bindings.Cmix] object so its methods can be wrapped to be
@@ -168,7 +167,7 @@ func LoadSynchronizedCmix(_ js.Value, args []js.Value) any {
 		net, err := bindings.LoadSynchronizedCmix(storageDir, password,
 			rs, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newCmixJS(net))
 		}
diff --git a/wasm/collective.go b/wasm/collective.go
index 5b35b3d4b597f0bf79543e1f38cb741c49ee8d24..77a8fdd35f3c5d45b57881b4cf895209aaab1a1d 100644
--- a/wasm/collective.go
+++ b/wasm/collective.go
@@ -12,6 +12,8 @@ package wasm
 import (
 	"syscall/js"
 
+	"gitlab.com/elixxir/wasm-utils/exception"
+
 	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/wasm-utils/utils"
 )
@@ -72,7 +74,7 @@ func (r *RemoteKV) Get(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		value, err := r.api.Get(key, version)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(value))
 		}
@@ -97,7 +99,7 @@ func (r *RemoteKV) Delete(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := r.api.Delete(key, version)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -128,7 +130,7 @@ func (r *RemoteKV) Set(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := r.api.Set(key, value)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -178,7 +180,7 @@ func (r *RemoteKV) Prefix(_ js.Value, args []js.Value) any {
 		newAPI, err := r.api.Prefix(prefix)
 
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newRemoteKvJS(newAPI))
 		}
@@ -193,7 +195,7 @@ func (r *RemoteKV) Root(_ js.Value, args []js.Value) any {
 		newAPI, err := r.api.Root()
 
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newRemoteKvJS(newAPI))
 		}
@@ -260,7 +262,7 @@ func (r *RemoteKV) StoreMapElement(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := r.api.StoreMapElement(mapName, elementKey, val, version)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -291,7 +293,7 @@ func (r *RemoteKV) StoreMap(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := r.api.StoreMap(mapName, val, version)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -321,7 +323,7 @@ func (r *RemoteKV) DeleteMapElement(_ js.Value, args []js.Value) any {
 		deleted, err := r.api.DeleteMapElement(mapName, elementKey,
 			version)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(deleted))
 		}
@@ -350,7 +352,7 @@ func (r *RemoteKV) GetMap(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		mapJSON, err := r.api.GetMap(mapName, version)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(mapJSON))
 		}
@@ -381,7 +383,7 @@ func (r *RemoteKV) GetMapElement(_ js.Value, args []js.Value) any {
 		deleted, err := r.api.GetMapElement(mapName, elementKey,
 			version)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(deleted))
 		}
@@ -411,7 +413,7 @@ func (r *RemoteKV) ListenOnRemoteKey(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		deleted, err := r.api.ListenOnRemoteKey(key, version, cb)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(deleted))
 		}
@@ -441,7 +443,7 @@ func (r *RemoteKV) ListenOnRemoteMap(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		deleted, err := r.api.ListenOnRemoteMap(mapName, version, cb)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(deleted))
 		}
@@ -487,7 +489,7 @@ func newRemoteStore(arg js.Value) *RemoteStore {
 func (rsCB *RemoteStore) Read(path string) ([]byte, error) {
 
 	fn := func() js.Value { return rsCB.read(path) }
-	v, err := utils.RunAndCatch(fn)
+	v, err := exception.RunAndCatch(fn)
 	if err != nil {
 		return nil, err
 	}
@@ -504,7 +506,7 @@ func (rsCB *RemoteStore) Read(path string) ([]byte, error) {
 //   - Catches any thrown errors (of type Error) and returns it as an error.
 func (rsCB *RemoteStore) Write(path string, data []byte) error {
 	fn := func() js.Value { return rsCB.write(path, utils.CopyBytesToJS(data)) }
-	_, err := utils.RunAndCatch(fn)
+	_, err := exception.RunAndCatch(fn)
 	return err
 }
 
@@ -518,7 +520,7 @@ func (rsCB *RemoteStore) Write(path string, data []byte) error {
 //   - Catches any thrown errors (of type Error) and returns it as an error.
 func (rsCB *RemoteStore) GetLastModified(path string) ([]byte, error) {
 	fn := func() js.Value { return rsCB.getLastModified(path) }
-	v, err := utils.RunAndCatch(fn)
+	v, err := exception.RunAndCatch(fn)
 	if err != nil {
 		return nil, err
 	}
@@ -532,7 +534,7 @@ func (rsCB *RemoteStore) GetLastModified(path string) ([]byte, error) {
 //   - Catches any thrown errors (of type Error) and returns it as an error.
 func (rsCB *RemoteStore) GetLastWrite() ([]byte, error) {
 	fn := func() js.Value { return rsCB.getLastWrite() }
-	v, err := utils.RunAndCatch(fn)
+	v, err := exception.RunAndCatch(fn)
 	if err != nil {
 		return nil, err
 	}
@@ -549,7 +551,7 @@ func (rsCB *RemoteStore) GetLastWrite() ([]byte, error) {
 //   - Catches any thrown errors (of type Error) and returns it as an error.
 func (rsCB *RemoteStore) ReadDir(path string) ([]byte, error) {
 	fn := func() js.Value { return rsCB.readDir(path) }
-	v, err := utils.RunAndCatch(fn)
+	v, err := exception.RunAndCatch(fn)
 	if err != nil {
 		return nil, err
 	}
diff --git a/wasm/connect.go b/wasm/connect.go
index 279b98512e9b6ad91b62f0b6428a4d510b8c1f78..b6b78e6940f1314d6d189e737d016db89112110b 100644
--- a/wasm/connect.go
+++ b/wasm/connect.go
@@ -108,7 +108,7 @@ func (c *Connection) SendE2E(_ js.Value, args []js.Value) any {
 // Close deletes this [Connection]'s [partner.Manager] and releases resources.
 //
 // Returns:
-//   - Throws a TypeError if closing fails.
+//   - Throws an error if closing fails.
 func (c *Connection) Close(js.Value, []js.Value) any {
 	err := c.api.Close()
 	if err != nil {
@@ -154,7 +154,7 @@ func (l *listener) Name() string { return l.name().String() }
 //     [bindings.Listener] interface.
 //
 // Returns:
-//   - Throws a TypeError is registering the listener fails.
+//   - Throws an error is registering the listener fails.
 func (c *Connection) RegisterListener(_ js.Value, args []js.Value) any {
 	err := c.api.RegisterListener(args[0].Int(),
 		&listener{utils.WrapCB(args[1], "Hear"), utils.WrapCB(args[1], "Name")})
diff --git a/wasm/delivery.go b/wasm/delivery.go
index 43c56a69d20c2da68228d2c701505c8938c86654..67bc677bc300ca0d5108b33dd5573f1f0d80c4ed 100644
--- a/wasm/delivery.go
+++ b/wasm/delivery.go
@@ -80,7 +80,7 @@ func (mdc *messageDeliveryCallback) EventCallback(
 //     occurs, in milliseconds (int).
 //
 // Returns:
-//   - Throws a TypeError if the parameters are invalid or getting round results
+//   - Throws an error if the parameters are invalid or getting round results
 //     fails.
 func (c *Cmix) WaitForRoundResult(_ js.Value, args []js.Value) any {
 	roundList := utils.CopyBytesToGo(args[0])
diff --git a/wasm/dm.go b/wasm/dm.go
index 5503799fc7bd48be7ddc465202b6a9b4079c30fb..101ce92bdb36884069e3c71e6bc63074828a0d9d 100644
--- a/wasm/dm.go
+++ b/wasm/dm.go
@@ -61,6 +61,7 @@ func newDMClientJS(api *bindings.DMClient) map[string]any {
 		"SendText":     js.FuncOf(cm.SendText),
 		"SendReply":    js.FuncOf(cm.SendReply),
 		"SendReaction": js.FuncOf(cm.SendReaction),
+		"SendSilent":   js.FuncOf(cm.SendSilent),
 		"Send":         js.FuncOf(cm.Send),
 	}
 
@@ -86,7 +87,7 @@ func newDMClientJS(api *bindings.DMClient) map[string]any {
 //
 // Returns:
 //   - Javascript representation of the [DMClient] object.
-//   - Throws a TypeError if creating the manager fails.
+//   - Throws an error if creating the manager fails.
 func NewDMClient(_ js.Value, args []js.Value) any {
 	privateIdentity := utils.CopyBytesToGo(args[1])
 	em := newDMReceiverBuilder(args[2])
@@ -129,7 +130,7 @@ func NewDMClient(_ js.Value, args []js.Value) any {
 // Returns:
 //   - Resolves to a Javascript representation of the [DMClient] object.
 //   - Rejected with an error if loading indexedDbWorker or the manager fails.
-//   - Throws a TypeError if the cipher ID does not correspond to a cipher.
+//   - Throws an error if the cipher ID does not correspond to a cipher.
 func NewDMClientWithIndexedDb(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
 	wasmJsPath := args[1].String()
@@ -474,6 +475,42 @@ func (dmc *DMClient) SendReaction(_ js.Value, args []js.Value) any {
 	return utils.CreatePromise(promiseFn)
 }
 
+// SendSilent is used to send to a channel a message with no notifications.
+// Its primary purpose is to communicate new nicknames without calling [Send].
+//
+// It takes no payload intentionally as the message should be very lightweight.
+//
+// Parameters:
+//   - args[0] - The bytes of the public key of the partner's ED25519
+//     signing key (Uint8Array).
+//   - args[1] - The token used to derive the reception ID for the partner
+//     (int).
+//   - args[2] - JSON of [xxdk.CMIXParams]. If left empty
+//     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
+//
+// Returns a promise:
+//   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
+//   - Rejected with an error if sending fails.
+func (dmc *DMClient) SendSilent(_ js.Value, args []js.Value) any {
+	var (
+		partnerPubKeyBytes = utils.CopyBytesToGo(args[0])
+		partnerToken       = int32(args[1].Int())
+		cmixParamsJSON     = utils.CopyBytesToGo(args[2])
+	)
+
+	promiseFn := func(resolve, reject func(args ...any) js.Value) {
+		sendReport, err := dmc.api.SendSilent(
+			partnerPubKeyBytes, partnerToken, cmixParamsJSON)
+		if err != nil {
+			reject(exception.NewTrace(err))
+		} else {
+			resolve(utils.CopyBytesToJS(sendReport))
+		}
+	}
+
+	return utils.CreatePromise(promiseFn)
+}
+
 // Send is used to send a raw message. In general, it
 // should be wrapped in a function that defines the wire protocol.
 //
@@ -962,7 +999,7 @@ func newDMDbCipherJS(api *bindings.DMDbCipher) map[string]any {
 //
 // Returns:
 //   - JavaScript representation of the [DMDbCipher] object.
-//   - Throws a TypeError if creating the cipher fails.
+//   - Throws an error if creating the cipher fails.
 func NewDMsDatabaseCipher(_ js.Value, args []js.Value) any {
 	cmixId := args[0].Int()
 	password := utils.CopyBytesToGo(args[1])
@@ -997,7 +1034,7 @@ func (c *DMDbCipher) GetID(js.Value, []js.Value) any {
 //
 // Returns:
 //   - The ciphertext of the plaintext passed in (Uint8Array).
-//   - Throws a TypeError if it fails to encrypt the plaintext.
+//   - Throws an error if it fails to encrypt the plaintext.
 func (c *DMDbCipher) Encrypt(_ js.Value, args []js.Value) any {
 	ciphertext, err := c.api.Encrypt(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -1018,7 +1055,7 @@ func (c *DMDbCipher) Encrypt(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - The plaintext of the ciphertext passed in (Uint8Array).
-//   - Throws a TypeError if it fails to encrypt the plaintext.
+//   - Throws an error if it fails to encrypt the plaintext.
 func (c *DMDbCipher) Decrypt(_ js.Value, args []js.Value) any {
 	plaintext, err := c.api.Decrypt(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -1033,7 +1070,7 @@ func (c *DMDbCipher) Decrypt(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of the cipher (Uint8Array).
-//   - Throws a TypeError if marshalling fails.
+//   - Throws an error if marshalling fails.
 func (c *DMDbCipher) MarshalJSON(js.Value, []js.Value) any {
 	data, err := c.api.MarshalJSON()
 	if err != nil {
@@ -1055,7 +1092,7 @@ func (c *DMDbCipher) MarshalJSON(js.Value, []js.Value) any {
 //
 // Returns:
 //   - JSON of the cipher (Uint8Array).
-//   - Throws a TypeError if marshalling fails.
+//   - Throws an error if marshalling fails.
 func (c *DMDbCipher) UnmarshalJSON(_ js.Value, args []js.Value) any {
 	err := c.api.UnmarshalJSON(utils.CopyBytesToGo(args[0]))
 	if err != nil {
diff --git a/wasm/dummy.go b/wasm/dummy.go
index b0fa6a82bad4f8e28290663eff285d002f10cafb..9efbc687eee46005192148f75f3164b81421a531 100644
--- a/wasm/dummy.go
+++ b/wasm/dummy.go
@@ -53,7 +53,7 @@ func newDummyTrafficJS(newDT *bindings.DummyTraffic) map[string]any {
 //
 // Returns:
 //   - Javascript representation of the DummyTraffic object.
-//   - Throws a TypeError if creating the manager fails.
+//   - Throws an error if creating the manager fails.
 func NewDummyTrafficManager(_ js.Value, args []js.Value) any {
 	dt, err := bindings.NewDummyTrafficManager(
 		args[0].Int(), args[1].Int(), args[2].Int(), args[3].Int())
@@ -75,7 +75,7 @@ func NewDummyTrafficManager(_ js.Value, args []js.Value) any {
 // thread will then be prevented from beginning another round of sending.
 //
 // Returns:
-//   - Throws a TypeError if it fails to send a pause signal to the sending
+//   - Throws an error if it fails to send a pause signal to the sending
 //     thread.
 func (dt *DummyTraffic) Pause(js.Value, []js.Value) any {
 	err := dt.api.Pause()
@@ -98,7 +98,7 @@ func (dt *DummyTraffic) Pause(js.Value, []js.Value) any {
 // sending interval after a call to Start.
 //
 // Returns:
-//   - Throws a TypeError if it fails to send a start signal to the sending
+//   - Throws an error if it fails to send a start signal to the sending
 //     thread.
 func (dt *DummyTraffic) Start(js.Value, []js.Value) any {
 	err := dt.api.Start()
diff --git a/wasm/e2e.go b/wasm/e2e.go
index 5f3debbca3bb52f2b5e594942eaed3c96bcabd5a..dcb4853d23745f0d85a879284a6b2c918a1d5f39 100644
--- a/wasm/e2e.go
+++ b/wasm/e2e.go
@@ -91,7 +91,7 @@ func (e *E2e) GetID(js.Value, []js.Value) any {
 //
 // Returns:
 //   - Javascript representation of the [E2e] object.
-//   - Throws a TypeError if logging in fails.
+//   - Throws an error if logging in fails.
 func Login(_ js.Value, args []js.Value) any {
 	callbacks := newAuthCallbacks(args[1])
 	identity := utils.CopyBytesToGo(args[2])
@@ -121,7 +121,7 @@ func Login(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - Javascript representation of the [E2e] object.
-//   - Throws a TypeError if logging in fails.
+//   - Throws an error if logging in fails.
 func LoginEphemeral(_ js.Value, args []js.Value) any {
 	callbacks := newAuthCallbacks(args[1])
 	identity := utils.CopyBytesToGo(args[2])
@@ -168,7 +168,7 @@ func (e *E2e) GetUdCertFromNdf(js.Value, []js.Value) any {
 //
 // Returns
 //   - Marshalled bytes of [contact.Contact] (Uint8Array).
-//   - Throws a TypeError if the contact file cannot be loaded.
+//   - Throws an error if the contact file cannot be loaded.
 func (e *E2e) GetUdContactFromNdf(js.Value, []js.Value) any {
 	b, err := e.api.GetUdContactFromNdf()
 	if err != nil {
diff --git a/wasm/e2eHandler.go b/wasm/e2eHandler.go
index 7f3b5966e886b7e77752da24a03f64ac369b01be..fd107b0b1390a9ec759e0cdfaf0a6544ee97d3e6 100644
--- a/wasm/e2eHandler.go
+++ b/wasm/e2eHandler.go
@@ -10,9 +10,10 @@
 package wasm
 
 import (
+	"syscall/js"
+
 	"gitlab.com/elixxir/wasm-utils/exception"
 	"gitlab.com/elixxir/wasm-utils/utils"
-	"syscall/js"
 )
 
 // GetReceptionID returns the marshalled default IDs.
@@ -200,13 +201,19 @@ type processor struct {
 //
 // Parameters:
 //   - message - Returns the message contents (Uint8Array).
+//   - tags - a byte array representing the tags on the message (Uint8Array)
+//   - metadata - other arbitrary metadata (Uint8Array)
 //   - receptionId - Returns the marshalled bytes of the sender's [id.ID]
 //     (Uint8Array).
 //   - ephemeralId - Returns the ephemeral ID of the sender (int).
 //   - roundId - Returns the ID of the round sent on (int).
-func (p *processor) Process(
-	message, receptionId []byte, ephemeralId, roundId int64) {
-	p.process(utils.CopyBytesToJS(message), utils.CopyBytesToJS(receptionId),
+func (p *processor) Process(message, tags, metadata, receptionId []byte,
+	ephemeralId int64, roundId int64) {
+
+	p.process(utils.CopyBytesToJS(message),
+		utils.CopyBytesToJS(tags),
+		utils.CopyBytesToJS(metadata),
+		utils.CopyBytesToJS(receptionId),
 		ephemeralId, roundId)
 }
 
@@ -233,7 +240,9 @@ func (p *processor) String() string {
 //   - Throws TypeError if registering the service fails.
 func (e *E2e) AddService(_ js.Value, args []js.Value) any {
 	p := &processor{
-		utils.WrapCB(args[1], "Process"), utils.WrapCB(args[1], "String")}
+		utils.WrapCB(args[1], "Process"),
+		utils.WrapCB(args[1], "String"),
+	}
 
 	err := e.api.AddService(args[0].String(), p)
 	if err != nil {
diff --git a/wasm/emoji.go b/wasm/emoji.go
index fd64418cdc7c14ac823c3115178d835efc88faf2..7be8ce8e45461c4e2ffb1a0f1f215cff313421a4 100644
--- a/wasm/emoji.go
+++ b/wasm/emoji.go
@@ -22,7 +22,7 @@ import (
 //
 // Returns:
 //   - JSON of an array of emoji.Emoji (Uint8Array).
-//   - Throws a TypeError if marshalling the JSON fails.
+//   - Throws an error if marshalling the JSON fails.
 //
 // Example JSON:
 //
@@ -69,7 +69,7 @@ func SupportedEmojis(js.Value, []js.Value) any {
 //
 // Returns:
 //   - JSON of a map of emoji.Emoji (Uint8Array).
-//   - Throws a TypeError if marshalling the JSON fails.
+//   - Throws an error if marshalling the JSON fails.
 //
 // Example JSON:
 //
diff --git a/wasm/errors.go b/wasm/errors.go
index baf9e8b095afed9b291bdfc9c793eaf3d70bea7f..2b2bab8c2ce6ebc762bad96c8e317de313207e6f 100644
--- a/wasm/errors.go
+++ b/wasm/errors.go
@@ -49,7 +49,7 @@ func CreateUserFriendlyErrorMessage(_ js.Value, args []js.Value) any {
 //	}
 //
 // Returns:
-//   - Throws a TypeError if the JSON cannot be unmarshalled.
+//   - Throws an error if the JSON cannot be unmarshalled.
 func UpdateCommonErrors(_ js.Value, args []js.Value) any {
 	err := bindings.UpdateCommonErrors(args[0].String())
 	if err != nil {
diff --git a/wasm/fileTransfer.go b/wasm/fileTransfer.go
index e91a8837cec438f4644e48407fdf2924a81eb1bc..033c410c1231685db46a9c88ea999d1c2be727f3 100644
--- a/wasm/fileTransfer.go
+++ b/wasm/fileTransfer.go
@@ -125,7 +125,7 @@ func (rpc *fileTransferReceiveProgressCallback) Callback(
 //
 // Returns:
 //   - Javascript representation of the [FileTransfer] object.
-//   - Throws a TypeError initialising the file transfer manager fails.
+//   - Throws an error initialising the file transfer manager fails.
 func InitFileTransfer(_ js.Value, args []js.Value) any {
 	rfc := &receiveFileCallback{utils.WrapCB(args[1], "Callback")}
 	e2eFileTransferParamsJson := utils.CopyBytesToGo(args[2])
@@ -186,7 +186,7 @@ func (f *FileTransfer) Send(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - File contents (Uint8Array).
-//   - Throws a TypeError the file transfer is incomplete or Receive has already
+//   - Throws an error the file transfer is incomplete or Receive has already
 //     been called.
 func (f *FileTransfer) Receive(_ js.Value, args []js.Value) any {
 	file, err := f.api.Receive(utils.CopyBytesToGo(args[0]))
@@ -209,7 +209,7 @@ func (f *FileTransfer) Receive(_ js.Value, args []js.Value) any {
 //   - args[0] - File transfer [fileTransfer.TransferID] (Uint8Array).
 //
 // Returns:
-//   - Throws a TypeError if the file transfer is incomplete.
+//   - Throws an error if the file transfer is incomplete.
 func (f *FileTransfer) CloseSend(_ js.Value, args []js.Value) any {
 	err := f.api.CloseSend(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -238,7 +238,7 @@ func (f *FileTransfer) CloseSend(_ js.Value, args []js.Value) any {
 //     triggering (int).
 //
 // Returns:
-//   - Throws a TypeError if registering the callback fails.
+//   - Throws an error if registering the callback fails.
 func (f *FileTransfer) RegisterSentProgressCallback(
 	_ js.Value, args []js.Value) any {
 	tidBytes := utils.CopyBytesToGo(args[0])
@@ -266,7 +266,7 @@ func (f *FileTransfer) RegisterSentProgressCallback(
 //     triggering (int).
 //
 // Returns:
-//   - Throws a TypeError if registering the callback fails.
+//   - Throws an error if registering the callback fails.
 func (f *FileTransfer) RegisterReceivedProgressCallback(
 	_ js.Value, args []js.Value) any {
 	tidBytes := utils.CopyBytesToGo(args[0])
diff --git a/wasm/follow.go b/wasm/follow.go
index b215bf5471ca7d5028a7d84d6128c61394e472e4..cfc49d4241bb0b87eb184685b4f521daa7105f03 100644
--- a/wasm/follow.go
+++ b/wasm/follow.go
@@ -10,10 +10,11 @@
 package wasm
 
 import (
+	"syscall/js"
+
 	"gitlab.com/elixxir/wasm-utils/exception"
 	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
-	"syscall/js"
 )
 
 // StartNetworkFollower kicks off the tracking of the network. It starts long-
@@ -54,7 +55,7 @@ import (
 //   - args[0] - Timeout when stopping threads in milliseconds (int).
 //
 // Returns:
-//   - Throws a TypeError if starting the network follower fails.
+//   - Throws an error if starting the network follower fails.
 func (c *Cmix) StartNetworkFollower(_ js.Value, args []js.Value) any {
 	err := c.api.StartNetworkFollower(args[0].Int())
 	if err != nil {
@@ -72,7 +73,7 @@ func (c *Cmix) StartNetworkFollower(_ js.Value, args []js.Value) any {
 // most likely be in an unrecoverable state and need to be trashed.
 //
 // Returns:
-//   - Throws a TypeError if the follower is in the wrong state to stop or if it
+//   - Throws an error if the follower is in the wrong state to stop or if it
 //     fails to stop.
 func (c *Cmix) StopNetworkFollower(js.Value, []js.Value) any {
 	err := c.api.StopNetworkFollower()
@@ -381,6 +382,53 @@ func (tsc *trackServicesCallback) Callback(marshalData []byte, err error) {
 	tsc.callback(utils.CopyBytesToJS(marshalData), exception.NewTrace(err))
 }
 
+// trackServicesCallback adheres to the [bindings.TrackServicesCallback]
+// interface.
+type trackCompressedServicesCallback struct {
+	callback func(args ...any) js.Value
+}
+
+// Callback is the callback for [Cmix.TrackCompressedServices]. This
+// will pass to the user a JSON-marshalled list of backend
+// services. If there was an error retrieving or marshalling the
+// service list, there is an error for the second parameter, which
+// will be non-null.
+//
+// Parameters:
+//   - marshalData - Returns the JSON of
+//     [message.CompressedServiceList] (Uint8Array).
+//   - err - Returns an error on failure (Error).
+//
+// Example JSON:
+//
+//		{
+//	   "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD": [
+//	     {
+//	       "Identifier": null,
+//	       "Tags": ["test"],
+//	       "Metadata": null
+//	     }
+//	   ],
+//	   "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD": [
+//	     {
+//	       "Identifier": null,
+//	       "Tags": ["test"],
+//	       "Metadata": null
+//	     }
+//	   ],
+//	   "AAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD": [
+//	     {
+//	       "Identifier": null,
+//	       "Tags": ["test"],
+//	       "Metadata": null
+//	     }
+//	   ]
+//	 }
+func (tcsc *trackCompressedServicesCallback) Callback(marshalData []byte,
+	err error) {
+	tcsc.callback(utils.CopyBytesToJS(marshalData), exception.NewTrace(err))
+}
+
 // TrackServicesWithIdentity will return via a callback the list of services the
 // backend keeps track of for the provided identity. This may be passed into
 // other bindings call which may need context on the available services for this
@@ -389,13 +437,20 @@ func (tsc *trackServicesCallback) Callback(marshalData []byte, err error) {
 // Parameters:
 //   - args[0] - ID of [E2e] object in tracker (int).
 //   - args[1] - Javascript object that has functions that implement the
-//     [bindings.ClientError] interface.
+//     [bindings.TrackServicesCallback] interface.
+//   - args[1] - Javascript object that has functions that implement the
+//     [bindings.TrackCompressedServicesCallback] interface.
 //
 // Returns:
 //   - Throws TypeError if the [E2e] ID is invalid.
 func (c *Cmix) TrackServicesWithIdentity(_ js.Value, args []js.Value) any {
 	err := c.api.TrackServicesWithIdentity(args[0].Int(),
-		&trackServicesCallback{utils.WrapCB(args[0], "Callback")})
+		&trackServicesCallback{
+			utils.WrapCB(args[0], "Callback"),
+		},
+		&trackCompressedServicesCallback{
+			utils.WrapCB(args[0], "Callback"),
+		})
 	if err != nil {
 		exception.ThrowTrace(err)
 		return nil
diff --git a/wasm/group.go b/wasm/group.go
index 459a07db0cd79815da19f5d86254150f88ad1748..e824d4ee85ce7610f99e6e63dba6ab56e0e58f2a 100644
--- a/wasm/group.go
+++ b/wasm/group.go
@@ -55,7 +55,7 @@ func newGroupChatJS(api *bindings.GroupChat) map[string]any {
 //
 // Returns:
 //   - Javascript representation of the [GroupChat] object.
-//   - Throws a TypeError if creating the [GroupChat] fails.
+//   - Throws an error if creating the [GroupChat] fails.
 func NewGroupChat(_ js.Value, args []js.Value) any {
 	requestFunc := &groupRequest{utils.WrapCB(args[1], "Callback")}
 	p := &groupChatProcessor{
@@ -136,7 +136,7 @@ func (g *GroupChat) ResendRequest(_ js.Value, args []js.Value) any {
 //     object returned over the bindings (Uint8Array).
 //
 // Returns:
-//   - Throws a TypeError if joining the group fails.
+//   - Throws an error if joining the group fails.
 func (g *GroupChat) JoinGroup(_ js.Value, args []js.Value) any {
 	err := g.api.JoinGroup(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -154,7 +154,7 @@ func (g *GroupChat) JoinGroup(_ js.Value, args []js.Value) any {
 //     can be found in the report returned by [GroupChat.MakeGroup].
 //
 // Returns:
-//   - Throws a TypeError if leaving the group fails.
+//   - Throws an error if leaving the group fails.
 func (g *GroupChat) LeaveGroup(_ js.Value, args []js.Value) any {
 	err := g.api.LeaveGroup(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -201,7 +201,7 @@ func (g *GroupChat) Send(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of array of [id.ID] representing all group ID's (Uint8Array).
-//   - Throws a TypeError if getting the groups fails.
+//   - Throws an error if getting the groups fails.
 func (g *GroupChat) GetGroups(js.Value, []js.Value) any {
 	groups, err := g.api.GetGroups()
 	if err != nil {
@@ -221,7 +221,7 @@ func (g *GroupChat) GetGroups(js.Value, []js.Value) any {
 //
 // Returns:
 //   - Javascript representation of the [GroupChat] object.
-//   - Throws a TypeError if getting the group fails.
+//   - Throws an error if getting the group fails.
 func (g *GroupChat) GetGroup(_ js.Value, args []js.Value) any {
 	grp, err := g.api.GetGroup(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -315,7 +315,7 @@ func (g *Group) GetCreatedMS(js.Value, []js.Value) any {
 //
 // Returns:
 //   - JSON of [group.Membership] (Uint8Array).
-//   - Throws a TypeError if marshalling fails.
+//   - Throws an error if marshalling fails.
 func (g *Group) GetMembership(js.Value, []js.Value) any {
 	membership, err := g.api.GetMembership()
 	if err != nil {
@@ -342,7 +342,7 @@ func (g *Group) Serialize(js.Value, []js.Value) any {
 //
 // Returns:
 //   - Javascript representation of the [GroupChat] object.
-//   - Throws a TypeError if getting the group fails.
+//   - Throws an error if getting the group fails.
 func DeserializeGroup(_ js.Value, args []js.Value) any {
 	grp, err := bindings.DeserializeGroup(utils.CopyBytesToGo(args[0]))
 	if err != nil {
diff --git a/wasm/identity.go b/wasm/identity.go
index 013d2059b31378b2ce2e580723ada3d59fa35697..0800fffa8d209ab1bfd2c004998ade87c45cf2b3 100644
--- a/wasm/identity.go
+++ b/wasm/identity.go
@@ -32,7 +32,7 @@ import (
 //   - args[2] - ID of [Cmix] object in tracker (int).
 //
 // Returns:
-//   - Throws a TypeError if the identity cannot be stored in storage.
+//   - Throws an error if the identity cannot be stored in storage.
 func StoreReceptionIdentity(_ js.Value, args []js.Value) any {
 	identity := utils.CopyBytesToGo(args[1])
 	err := bindings.StoreReceptionIdentity(
@@ -55,7 +55,7 @@ func StoreReceptionIdentity(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of the stored [xxdk.ReceptionIdentity] object (Uint8Array).
-//   - Throws a TypeError if the identity cannot be retrieved from storage.
+//   - Throws an error if the identity cannot be retrieved from storage.
 func LoadReceptionIdentity(_ js.Value, args []js.Value) any {
 	ri, err := bindings.LoadReceptionIdentity(args[0].String(), args[1].Int())
 	if err != nil {
@@ -127,7 +127,7 @@ func (c *Cmix) GetReceptionRegistrationValidationSignature(
 //
 // Returns:
 //   - Marshalled bytes of [contact.Contact] (string).
-//   - Throws a TypeError if unmarshalling the identity fails.
+//   - Throws an error if unmarshalling the identity fails.
 func GetContactFromReceptionIdentity(_ js.Value, args []js.Value) any {
 	// Note that this function does not appear in normal bindings
 	identityJSON := utils.CopyBytesToGo(args[0])
@@ -147,7 +147,7 @@ func GetContactFromReceptionIdentity(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - Marshalled bytes of [id.ID] (Uint8Array).
-//   - Throws a TypeError if loading the ID from the contact file fails.
+//   - Throws an error if loading the ID from the contact file fails.
 func GetIDFromContact(_ js.Value, args []js.Value) any {
 	cID, err := bindings.GetIDFromContact(utils.CopyBytesToGo(args[0]))
 	if err != nil {
@@ -166,7 +166,7 @@ func GetIDFromContact(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - Bytes of the [cyclic.Int] object (Uint8Array).
-//   - Throws a TypeError if loading the public key from the contact file fails.
+//   - Throws an error if loading the public key from the contact file fails.
 func GetPubkeyFromContact(_ js.Value, args []js.Value) any {
 	key, err := bindings.GetPubkeyFromContact([]byte(args[0].String()))
 	if err != nil {
@@ -190,7 +190,7 @@ func GetPubkeyFromContact(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - Marshalled bytes of the modified [contact.Contact] (string).
-//   - Throws a TypeError if loading or modifying the contact fails.
+//   - Throws an error if loading or modifying the contact fails.
 func SetFactsOnContact(_ js.Value, args []js.Value) any {
 	marshaledContact := utils.CopyBytesToGo(args[0])
 	factListJSON := utils.CopyBytesToGo(args[1])
@@ -210,7 +210,7 @@ func SetFactsOnContact(_ js.Value, args []js.Value) any {
 //
 // Returns:
 //   - JSON of [fact.FactList] (Uint8Array).
-//   - Throws a TypeError if loading the contact fails.
+//   - Throws an error if loading the contact fails.
 func GetFactsFromContact(_ js.Value, args []js.Value) any {
 	fl, err := bindings.GetFactsFromContact(utils.CopyBytesToGo(args[0]))
 	if err != nil {
diff --git a/wasm/notifications.go b/wasm/notifications.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a4887292d140e22bbb8b14ee8d87ce1a72fbdff
--- /dev/null
+++ b/wasm/notifications.go
@@ -0,0 +1,131 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+package wasm
+
+import (
+	"syscall/js"
+
+	"gitlab.com/elixxir/client/v4/bindings"
+	"gitlab.com/elixxir/client/v4/notifications"
+	"gitlab.com/elixxir/wasm-utils/exception"
+)
+
+type Notifications struct {
+	api bindings.Notifications
+}
+
+// newNotificationsJS wrapts the bindings Noticiation object and implements
+// wrappers in JS for all it's functionality.
+func newNotificationsJS(api bindings.Notifications) map[string]any {
+	n := Notifications{api}
+	notificationsImplJS := map[string]any{
+		"AddToken":    js.FuncOf(n.AddToken),
+		"RemoveToken": js.FuncOf(n.RemoveToken),
+		"SetMaxState": js.FuncOf(n.SetMaxState),
+		"GetMaxState": js.FuncOf(n.GetMaxState),
+		"GetID":       js.FuncOf(n.GetID),
+	}
+	return notificationsImplJS
+}
+
+// LoadNotifications returns a JS wrapped implementation of
+// [bindings.Notifications].
+//
+// Parameters:
+//   - args[0] - the cMixID integer
+//
+// Returns a notifications object or throws an error
+func LoadNotifications(_ js.Value, args []js.Value) any {
+	cMixID := args[0].Int()
+	api, err := bindings.LoadNotifications(cMixID)
+	if err != nil {
+		exception.ThrowTrace(err)
+		return nil
+	}
+
+	return newNotificationsJS(api)
+}
+
+// LoadNotificationsDummy returns a JS wrapped implementation of
+// [bindings.Notifications] with a dummy notifications implementation.
+//
+// Parameters:
+//   - args[0] - the cMixID integer
+//
+// Returns a notifications object or throws an error
+func LoadNotificationsDummy(_ js.Value, args []js.Value) any {
+	cMixID := args[0].Int()
+	api, err := bindings.LoadNotificationsDummy(cMixID)
+	if err != nil {
+		exception.ThrowTrace(err)
+		return nil
+	}
+
+	return newNotificationsJS(api)
+}
+
+// GetID returns the bindings ID for the [bindings.Notifications] object
+func (n *Notifications) GetID(js.Value, []js.Value) any {
+	return n.api.GetID()
+}
+
+// AddToken implements [bindings.Notifications.AddToken].
+//
+// Parameters:
+//   - args[0] - newToken string
+//   - args[1] - app string
+//
+// Returns nothing or an error (throwable)
+func (n *Notifications) AddToken(_ js.Value, args []js.Value) any {
+	newToken := args[0].String()
+	app := args[1].String()
+
+	err := n.api.AddToken(newToken, app)
+	if err != nil {
+		exception.ThrowTrace(err)
+	}
+
+	return nil
+}
+
+// RemoveToken implements [bindings.Notifications.RemoveToken].
+//
+// Returns nothing or throws an error.
+func (n *Notifications) RemoveToken(_ js.Value, args []js.Value) any {
+	err := n.api.RemoveToken()
+	if err != nil {
+		exception.ThrowTrace(err)
+	}
+	return nil
+}
+
+// SetMaxState implements [bindings.Notifications.SetMaxState]
+//
+// Parameters:
+//   - args[0] - maxState integer
+//
+// Returns nothing or throws an error
+func (n *Notifications) SetMaxState(_ js.Value, args []js.Value) any {
+	maxState := int64(args[0].Int())
+
+	err := n.api.SetMaxState(notifications.NotificationState(maxState))
+	if err != nil {
+		exception.ThrowTrace(err)
+	}
+
+	return nil
+}
+
+// GetMaxState implements [bindings.Notifications.GetMaxState]
+//
+// Returns the current maxState integer
+func (n *Notifications) GetMaxState(_ js.Value, args []js.Value) any {
+	return int64(n.api.GetMaxState())
+}
diff --git a/wasm/restlikeSingle.go b/wasm/restlikeSingle.go
index 7a2e8f8c2828b3c3b91b56e0ba5cce3ed45ef548..41118d7f6d6ae32960712ea2f0436a927d28255d 100644
--- a/wasm/restlikeSingle.go
+++ b/wasm/restlikeSingle.go
@@ -80,7 +80,7 @@ func RequestRestLike(_ js.Value, args []js.Value) any {
 //     [bindings.RestlikeCallback] interface.
 //
 // Returns:
-//   - Throws a TypeError if parsing the parameters or making the request fails.
+//   - Throws an error if parsing the parameters or making the request fails.
 func AsyncRequestRestLike(_ js.Value, args []js.Value) any {
 	e2eID := args[0].Int()
 	recipient := utils.CopyBytesToGo(args[1])
diff --git a/wasm/single.go b/wasm/single.go
index 5d6aa92eef53055bf8316f5dfaedea1b81746f97..3528a0bea1d27896c28e35c94960a9cf10fa1034 100644
--- a/wasm/single.go
+++ b/wasm/single.go
@@ -72,7 +72,7 @@ func TransmitSingleUse(_ js.Value, args []js.Value) any {
 // Returns:
 //   - Javascript representation of the [Stopper] object, an interface
 //     containing a function used to stop the listener.
-//   - Throws a TypeError if listening fails.
+//   - Throws an error if listening fails.
 func Listen(_ js.Value, args []js.Value) any {
 	cb := &singleUseCallback{utils.WrapCB(args[2], "Callback")}
 	api, err := bindings.Listen(args[0].Int(), args[1].String(), cb)
diff --git a/wasm/ud.go b/wasm/ud.go
index 8b69f196b6362c3116551a9f78a0191d549fa571..cce7c8a7fecc09f6690c42de5891b0910ba3d5f2 100644
--- a/wasm/ud.go
+++ b/wasm/ud.go
@@ -100,7 +100,7 @@ func (uns *udNetworkStatus) UdNetworkStatus() int {
 // Returns:
 //   - Javascript representation of the [UserDiscovery] object that is
 //     registered to the specified UD service.
-//   - Throws a TypeError if creating or loading fails.
+//   - Throws an error if creating or loading fails.
 func NewOrLoadUd(_ js.Value, args []js.Value) any {
 	e2eID := args[0].Int()
 	follower := &udNetworkStatus{utils.WrapCB(args[1], "UdNetworkStatus")}
@@ -146,7 +146,7 @@ func NewOrLoadUd(_ js.Value, args []js.Value) any {
 // Returns:
 //   - Javascript representation of the [UserDiscovery] object that is loaded
 //     from backup.
-//   - Throws a TypeError if getting UD from backup fails.
+//   - Throws an error if getting UD from backup fails.
 func NewUdManagerFromBackup(_ js.Value, args []js.Value) any {
 	e2eID := args[0].Int()
 	follower := &udNetworkStatus{utils.WrapCB(args[1], "UdNetworkStatus")}
diff --git a/wasm/version.go b/wasm/version.go
index ca70bcbb150125aa427a271374632b4cef6118d8..048c49b585eb3196d8e1c3926cb87a6c7bf9556e 100644
--- a/wasm/version.go
+++ b/wasm/version.go
@@ -67,7 +67,7 @@ type VersionInfo struct {
 //
 // Returns:
 //   - JSON of [VersionInfo] (Uint8Array).
-//   - Throws a TypeError if getting the version failed.
+//   - Throws an error if getting the version failed.
 func GetWasmSemanticVersion(js.Value, []js.Value) any {
 	vi := VersionInfo{
 		Current: storage.SEMVER,
@@ -92,7 +92,7 @@ func GetWasmSemanticVersion(js.Value, []js.Value) any {
 //
 // Returns:
 //   - JSON of [VersionInfo] (Uint8Array).
-//   - Throws a TypeError if getting the version failed.
+//   - Throws an error if getting the version failed.
 func GetXXDKSemanticVersion(js.Value, []js.Value) any {
 	vi := VersionInfo{
 		Current: bindings.GetVersion(),