How to send a message in WebSockets 🔌 on the Internet Computer

I am using WebSockets 🔌 on the Internet Computer: Getting Started | by Luca Bertelli | Aug, 2023 | Medium this tutorial in order to send message from backend

fn accept_frind_request(fridnd_id: Principal, msg: AppMessage) -> String {
// ... accept_frind_request login here
// ws code
    ID_STORE.with(|user_files| {
        let user_files = user_files.borrow();
        let key = user_files.get("client_key").unwrap();
        send_app_message(key.clone(), msg);

pub fn on_open(args: OnOpenCallbackArgs) {
    let msg = AppMessage {
        text: String::from("is open"),
    send_app_message(args.client_key.clone(), msg);
    ID_STORE.with(|mut id_store| {
            .insert("client_key".to_string(), args.client_key);

I tried to store the client_key from on_open but in fact on_open is never used.

  • Goal I want to send_app_message(key.clone(), msg); a message to the user to niffy them that their friend request has been excpeted. Also, the message should be send to spesfic user (channel) and others should not be able to access it.
    can I also use caller() instead of client_key?
1 Like

It’s great that you tried our tutorial!

This is something that shouldn’t happen. Every time a new client connects, the on_open callback is called by the CDK on the canister. If this doesn’t happen, it means that the connection is still not fully established. This appears to be a bug, that we also have encountered and that we’re fixing. As a temporary workaround, reinstalling the canister should fix it (but you’re going to loose all the canister data, so be careful!).

We’re working on a more robust protocol for the IC WebSocket, that we’ll release soon. We’ll also update the tutorial accordingly. With this update, you’ll be able to use the client’s principal instead of that client_key. So, to answer your question:

No, you can’t with the current version. What you can do for now is to create a map client principal → client_key in order to find the right client_key to pass to the send_app_message function.

If you call the ws_send passing a specific client_key, the underlying IC WebSocket protocol takes care of delivering it to the right client and only that one.

P.S.: I’ve reached you out in DM if you want to have a call together, so that we can help you out better.


@AliSci we’ve updated the tutorial with the new versions (commit hashes) of both the JS SDK and the Rust CDK that should fix the bug. Reporting them here also:

  • Rust CDK: a1d05625f96faa12cad484afcdd01ddad39bb28c
  • Javascript SDK: 41e37667c36d26a535d4247e37711f4bad2184a8

Here’s how you can update the SDKs in your project.

Rust canister:

In the canister’s Cargo.toml, change the ic-websocket-cdk dependency to be:

ic-websocket-cdk = { git = "", rev = "a1d05625f96faa12cad484afcdd01ddad39bb28c" }

and then run:

cargo update

Javascript Frontend:

Simply run:

npm install --save

Let me know if it works.

error[E0308]: mismatched types
   --> src/ic_websocket_example_backend/src/
41  |     if let Err(e) = ws_send(client_key, msg) {
    |                     -------             ^^^ expected `Vec<u8>`, found `AppMessage`
    |                     |
    |                     arguments to this function are incorrect
    = note: expected struct `std::vec::Vec<u8>`
               found struct `AppMessage`
note: function defined here
   --> /Users/ahmed/.cargo/git/checkouts/ic-websocket-cdk-rs-b5112c5b627c7f56/a1d0562/src/ic-websocket-cdk/src/
953 | pub fn ws_send(client_key: ClientPublicKey, msg_bytes: Vec<u8>) -> CanisterWsSendResult {
    |        ^^^^^^^

1 Like

You have to serialize the message to a Candid bytes array before sending it. This will change in the next release.
To serialize the message, you can use the candid::encode_one function, like we do in the ic_websocket_example:

let msg_bytes = encode_one(&msg).unwrap()
if let Err(e) = ws_send(client_key, msg_bytes) {
    println!("Could not send message: {}", e);

To make things simpler, you can define an implementation for your AppMessage struct, like:

Please note that you’ll have to serialize/deserialize the message also on the frontend. Have a look at the idl.ts file of the ic_websocket_example:

Again, in the next version both the serialization and deserialization of your messages will be handled automatically by the SDKs, so that you don’t have to care about it.

1 Like

You still have a bug I think.

error[E0277]: the trait bound `ic_websocket_cdk::CanisterWsRegisterArguments: CandidType` is not satisfied
fn ws_register(args: CanisterWsRegisterArguments) -> CanisterWsRegisterResult {

Are you using the candid crate or the re-exports from the id_cdk crate?

In the latter case, please switch to the candid crate.

In the former case, which version of the candid crate are you using? The IC WebSocket CDK is using the version 0.9.3, so I’d suggest you to use the same version for your canister.

Let me know.

use ic_cdk_macros::*;

use handlers::{on_close, on_message, on_open};
use ic_websocket_cdk::{
    CanisterWsCloseArguments, CanisterWsCloseResult, CanisterWsGetMessagesArguments,
    CanisterWsGetMessagesResult, CanisterWsMessageArguments, CanisterWsMessageResult,
    CanisterWsOpenArguments, CanisterWsOpenResult, CanisterWsRegisterArguments,
    CanisterWsRegisterResult, WsHandlers,

use candid::candid_method;

mod handlers;

// Paste here the principal of the gateway obtained when running the gateway
pub const GATEWAY_PRINCIPAL: &str = "<gateway-principal>";

fn init() {
    let handlers = WsHandlers {
        on_open: Some(on_open),
        on_message: Some(on_message),
        on_close: Some(on_close),
    ic_websocket_cdk::init(handlers, GATEWAY_PRINCIPAL);

fn post_upgrade() {
    let handlers = WsHandlers {
        on_open: Some(on_open),
        on_message: Some(on_message),
        on_close: Some(on_close),
    ic_websocket_cdk::init(handlers, GATEWAY_PRINCIPAL);

// method called by the client SDK when instantiating a new IcWebSocket
fn ws_register(args: CanisterWsRegisterArguments) -> CanisterWsRegisterResult {

// method called by the WS Gateway after receiving FirstMessage from the client
fn ws_open(args: CanisterWsOpenArguments) -> CanisterWsOpenResult {

// method called by the Ws Gateway when closing the IcWebSocket connection
fn ws_close(args: CanisterWsCloseArguments) -> CanisterWsCloseResult {

// method called by the frontend SDK to send a message to the canister
fn ws_message(args: CanisterWsMessageArguments) -> CanisterWsMessageResult {

// method called by the WS Gateway to get messages for all the clients it serves
fn ws_get_messages(args: CanisterWsGetMessagesArguments) -> CanisterWsGetMessagesResult {
name = "ic_websocket_example_backend"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at

crate-type = ["cdylib"]

ic-websocket-cdk = { git = "", rev = "a1d05625f96faa12cad484afcdd01ddad39bb28c", version = "0.1.0" }

serde_cbor = "0.11.2"

dotenv = "0.15.0"
lazy_static = "1.4"
serde = "1.0.176"

candid = "0.9.3"
ic-cdk = "0.9.2"
ic-cdk-macros = "0.7.0"
ic-cdk-timers = "0.2.0"

I am doing it just like this

What’s the error now?

By the way, you should also update the following dependencies, according to the versions used by the IC WebSocket CDK:

It is The same error, nothing changed and

@AliSci is your code publicly available? Or is there a way I can see it? I think this way I can help you better.

Please also keep in mind that we’ll release the next version of the protocol (and so the SDKs and the Gateway) by the end of September and all these bugs should be solved. Thanks for your patience :smiley: