Webhooks & Server-to-server Oauth: Websocket fails to connect

I am creating a WebHooks-type app that uses server-to-server Oauth authentication.

I am following the Server-to-server Oauth guidelines (here), but when I attempt to open the websocket, I always get this response:

{"module":"build_connection","success":false,"content":"token is empty","header":{}}

I am seeing token is empty even though I am setting the Request Header Authorization with a valid bearer token.

I make an initial call to https://zoom.us/oauth/token?grant_type=account_credentials&account_id={accountId} as per the instructions here. This is successful and I am able to retrieve a valid token; example follows:

"scope":"phone:read:admin phone_e911:read:admin user:read:admin user:write:admin"}

I then create the websocket endpoint, using the URL specified in the App Feature section of the app definition:

I can see the Authorization header in the socket definition.

 map[Authorization:[Bearer [REDACTED]] Token:[REDACTED]]

… but the websocket connect attempt always returns:

{"module":"build_connection","success":false,"content":"token is empty","header":{}}

… and then disconnects.

The websocket connection attempt always returns:

{"module":"build_connection","success":false,"content":"token is empty","header":{}}

… and then disconnects.

API Endpoint/s?


How To Reproduce
As per the Process section above.

Additional context
Server is coded in Go, using the github.com/sacOO7/gowebsocket package, which itself relies on github.com/gorilla/websocket


Thank you for posting in the Developer Forum. It seems like the access token is not being passed into the request as expected. Since you have the access token, you should be able to make requests against Zoom API. Could you share more details about the intended flow? Specifically, what is the expected result on a successful WebSocket connect attempt? Would you be able to share a snippet of code that is preventing you from leveraging the access token to make a request?

Have you tried to manually/hardcode the access token at the websocket connect attempt step?

Also, to align with our security practices, we ask that you omit your Personal Identifiable Information when posting on the forum.

Hello, and thanks for taking the time to reply.

Could you share more details about the intended flow?

Sure. We have been using Webhooks apps for a while to get event data. We created a new app using Server to server OAuth, and see that we can choose either rest or websockets. We have chosen websockets because this is a realtime application, and the consistent connection should improve the event latency.

  1. Make a call to https://zoom.us/oauth/token to retrieve a token. This step is successful and a valid token is returned.
  2. Establish a websocket connection to the wss URL specified in the app’s Feature section. I am assuming that we need to add the retrieved token as an Authorization header in this connection attempt. However, I have not found any documentation relating to websockets that states this. If you have a pointer for me it would be much appreciated.

Specifically, what is the expected result on a successful WebSocket connect attempt?

At the very least we would expect a response indicating a successful connection. The response we get is of an unsuccessful connection, and then a disconnect.

Would you be able to share a snippet of code that is preventing you from leveraging the access token to make a request?

Certainly. In the below:

  • accountId, clientId and clientSecret are the values from the App Credentials section
  • ws is a string holding the Endpoint URL from the app Feature section
  • gws is a reference to the Gorilla Websocket package.
func main2() {
	var err error
	accessToken, err = GetAccessToken(accountId, clientId, clientSecret)
	if err != nil {
		log.Fatalf("Could not get an access token: %v", err)
	f("Token: %v", accessToken)

	header := http.Header{
		"Host":          {"ws.zoom.us"},
		"Content-Type":  {"application/json"},
		"Authorization": {"Bearer " + accessToken.AccessToken},

	f("Header: %v", header)
	c, _, err := gws.DefaultDialer.Dial(ws, header)
	if err != nil {
		log.Fatal("dial:", err)
	defer c.Close()

	done := make(chan struct{})

	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)

	go func() {
		defer close(done)
		for {
			_, message, err := c.ReadMessage()
			f("Message: %v", string(message))
			if err != nil {
				log.Println("read:", err)
				interrupt <- syscall.SIGTERM
			log.Printf("recv: %s", message)

Here is the log output:

2022/06/07 15:40:34 Token: {[REDACTED] bearer 3599 phone:read:admin phone_e911:read:admin user:read:admin user:write:admin}
2022/06/07 15:40:34 connecting to wss://ws.zoom.us/ws?subscriptionId=XXXXXXXXXXXX
2022/06/07 15:40:34 Header: map[Authorization:[Bearer [REDACTED]] Content-Type:[application/json] Host:[ws.zoom.us]]
2022/06/07 15:40:35 Message: {"module":"build_connection","success":false,"content":"token is empty","header":{}}
2022/06/07 15:40:35 recv: {"module":"build_connection","success":false,"content":"token is empty","header":{}}
2022/06/07 15:40:35 Message: 
2022/06/07 15:40:35 read: websocket: close 1000 (normal): Bye
2022/06/07 15:40:35 interrupt

There you go.

Thank you very much. I’ll give that a try.

Where should I have found the documentation for that?

@callum.katene the websockets feature is in beta at the moment. Please DM me if you are interested in learning more.


I would like to learn more. Can’t see how to DM unfortunately - still a little new to this.

What’s the best way to contact you?


@callum.katene sorry for the delay here. We will be sending more info regarding WebSockets Beta soon

That would be great, thanks. WebSockets offers significant advantages, so looking forward to doing more with this.

Hi @callum.katene

Please fill out this form if you are interested in the websockets beta. Thank you for your patience