401 error when downloading recording with Server-to-Server OAuth token

I’m attempting to download a recording using my Server-to-Server OAuth app’s access token but I’m getting a 401 HTTP error code. I can successfully download the URL using the token from my JWT app. This appears to be a variation of the bug that was discussed in this thread:

To get my OAuth token, I make a request like this:

curl -s -H 'Accept: application/json' -XPOST --user [client id]:[client secret] 'https://zoom.us/oauth/token?grant_type=account_credentials&account_id=[account id]' | jq -r .access_token > /tmp/zoom-oauth

I then use the “access_token” to make a request for the recording:

httpz://my-org.zoom.us/rec/webhook_download/[long string]?access_token=[access token]

I can also download using the “download_token” provided in the webhook request. However, this only lasts 24 hours. If I want to download the recording later, I need to use the OAuth token method.

Any ideas on whether this is a bug or something I’m missing in the way I make my requests?



1 Like

Hi @david5 , is your server-to-server OAuth app’s access token still valid? They expire after an hour.

@elisa.zoom @ojus.zoom can you confirm server-to-server OAuth access tokens can be used in the access token parameter for cloud recording webhook dowload?

Thank you!

Yes, the access token is valid. I generated a fresh one right before doing the download.

Okay thanks David. Please allow for a response from my colleagues that I’ve tagged above.

Appreciate your patience,

Hi @david5
I am trying to debug this issue but I am getting a different behavior.

Could you please confirm the steps that you are taking to make this request, along with the endpoints you are calling so I can try and replicate this on my end?

I also wanted to add that the access_token generated with the Server-to-Server OAuth app, it is only valid for 1 hour so you will have to generate a new one once it expires.


@elisa.zoom As mentioned in my initial report, I’m taking the following steps:

First, I generate a Server-to-Server OAuth access token using my account id, client id and client secret:

curl -s -H 'Accept: application/json' -XPOST --user [client id]:[client secret] 'https://zoom.us/oauth/token?grant_type=account_credentials&account_id=[account id]' | jq -r .access_token > /tmp/zoom-oauth

I then take the download_url from the recording.complete webhook message I receive and add the access_token query parameter with the value genrated from the step above:

curl -vs -o /tmp/recording.mp4 'https://my-org.zoom.us/rec/webhook_download/[long string]?access_token=[access token]'

When I attempt to run this download, I get an HTTP 401 response. When I use my JWT access token (which I’m trying to retire, per the deprecation guide from Zoom) I get a 301 redirect to a URL that contains the MP4 file.

@elisa.zoom Any progress on this? I’m beginning to wonder if Server-to-Server OAuth isn’t quite ready for prime time and we should go back to using JWT until the kinks are worked out over the next few months.

Hi @david5
Thank you for your patience and for sharing more details with me.
I have been trying to replicate this issue on my end and I was able to do so.
I will reach out to our Engineering team about this and I will come back to you with an update.
I do not know if this is expected or if this is a bug on our end. (ZSEE-62252 internal ticket for reference)

I will update you shortly.

Hi @david5
I did some testing on my end and what I did was that I grabbed the download_url from the payload that I received with me recording.complete webhook and then I used the download_url along with the download_token received in the same payload to get the recording, like so:

curl --location --request GET 'https://us02web.zoom.us/rec/webhook_download/{long_String}?access_token={download_token_from_payload}' \

Could you please give this a try and let me know if this works?

@elisa.zoom Using the download_token from the webhook message works fine (always has). The problem is that the token is only good for 1 hour after the message is received. If I need to reprocess the message or download the recording at a later date, I am unable to do so because my Server-to-Server OAuth token is not accepted in this scenario.

Right… let me reach out to the proper team about this and will come back to you with an update.

@elisa.zoom Curious what you were able to find related to this particular issue?

I am having the same issue with trying to download a recording using a server-to-server OAuth access token and I am getting a 401 error, whereas if I use my old JWT token it works fine. I’m wondering if there is any progress on this issue?

@elisa.zoom What were you able to find regarding this issue?

Hi @david5
Unfortunately I do not have any updates at the moment

@elisa.zoom I was wondering if there were any updates here? It seems we’re not receiving the “download_token” field in our “recording.completed” webhook event and still are not able to use the Server to Server OAuth access token for downloading. There’s no good workaround other than to fail back to the JWT app type for this particular use case which I’d like to avoid if possible.

@david5 @pfa_zoom please confirm the following :

  • recording:read:admin scope on Server-to-Server OAuth app
  • That you have the Recording management and View the recording content permissions assigned on the user role that’s trying to download the recording


Hi, All
I have the same problem.
I think zoom will shutdown JWT app type end of next month. Is the issue fixed? If no, We will lost way to download recording file without only 1 hour token. That is very bad for our program.


Hi @daichi ,

Can you please confirm the following?:


Yes, My app have following scopes

View and manage sub account’s user meetings /meeting:masterDelete
View all user meetings /meeting:read:adminDelete
Get a meeting’s encoded SIP URI /meeting:read:admin:sip_dialingDelete
View and manage all user meetings /meeting:write:adminDelete
View live streaming meeting token information /meeting_token:read:admin:live_streamingDelete
View local archiving meeting token information /meeting_token:read:admin:local_archivingDelete
This scope allows an app to view an account’s users’ local recording meeting token information /meeting_token:read:admin:local_recordingDelete
View all user recordings /recording:read:adminDelete
View and manage all user recordings /recording:write:adminDelete
View all user information /user:read:adminDelete

Currently the app uses download_token instead.
Because s2s’s token was not pass authentication with download.