Reverse Engineering the Clubhouse API

4-min read

One can ostensibly create a network graph of their most relevant potential users based on a few curl requests to Clubhouses undocumented API.
Disclaimer: I do not support reverse engineering any systems without authorized permission. This was purely an academic exercise.

Purpose

Finding users who are interested in your product is hard. Scratch that — finding users is hard. Clubhouse simplifies this by allowing their users to follow topics they are interested in, and joining clubs they fancy. Unfortunately clicking around on their mobile-only platform is a waste of time. I wanted a way to get the public information of Clubhouse users without manual work.

Armed with this information it should be trivial to create a master list of your target demographic who have specific interests, say journalists interested in improving their marketing skills.

I haven’t been timed out by Clubhouse yet.

Requirements

  1. You’ll need a clubhouse account (which requires a verified phone number).

  2. mitmproxy (optional) — All requests need to be authorized with a bearer token which can be grabbed via mitmproxy and tunneling through your PC as proxy. For the sake of conciseness I’ve chosen to not include them, finding the auth_check calls are trivial. From my understanding it is best to do this on your mobile since their might be additional tracking information and one might be painted as a bad actor.

Technicals

There are many additional headers and parameters which I’ve omitted for brevity. One can include those if needed, but I’ve included the bare minimum below to get a successful response from the servers.

POST | Get Your Profile

curl --location --request POST 'https://www.clubhouseapi.com/api/me' \
--header 'Authorization: Token {{YOUR_TOKEN_HERE}}'

The token should persist but refresh_tokens are also provided in the response.

All the relevant data should be under user_profile, in this format:

"user_profile": {
    "user_id": 578458,
    "name": "Viren Mohindra",
    "photo_url": "https://d14u0p1qkech25.cloudfront.net/578458_38c43e6d-b258-4301-9849-9f7748837880_thumbnail_250x250",
    "username": "virenmohindra"
},

GET | Get Club Members by Club ID

Required: club_id needs to be passed in as a parameter.

curl --location --request GET 'https://www.clubhouseapi.com/api/get_club_members?club_id={{CLUB_ID}}' \
--header 'Authorization: Token {{YOUR_TOKEN_HERE}}'

Response will be a list of Users, in this format:

{
    "user_id": 532095,
    "name": "Professor",
    "photo_url": "https://d14u0p1qkech25.cloudfront.net/532095_8a6db39e-7da0-4938-ab4c-1ab37c2f73cd_thumbnail_250x250",
    "username": "jocelynjkopac",
    "bio": "MY CLUBHOUSE BOOK JUST LAUNCH!!! 🎉🎉🎉 \nIt's on Amazon: Social Audio Jumpstart ✊🏾\n…",
    "is_admin": true,
    "is_leader": false,
    "is_member": true,
    "is_follower": false,
    "is_pending_accept": false,
    "is_pending_approval": false
},

POST | Get User by User ID

Required: user_id needs to be passed in as a parameter.
Note: Information needs to be x-www-form-urlencoded in the body.

Sometimes this particular request will hang if you haven’t provided the logging_context. If you’re following along with mitmproxy you might just have a JSON object handy. Use this nifty StackOverflow answer to convert the JSON object into a properly formatted value.

curl --location --request POST 'https://www.clubhouseapi.com/api/get_profile' \
--header 'Authorization: Token {{YOUR_TOKEN_HERE}}' \
--data-urlencode 'user_id={{USER_ID}}'

The most important fields would be the user_profile which houses all verified social handles and the list of clubs and topics.

An example sanitized response can be found as a Github Gist here.

Good luck!