webull icon indicating copy to clipboard operation
webull copied to clipboard

wb.login() is returning 403

Open apimir opened this issue 3 months ago • 53 comments

Today, logging into Webull via the Python webull library started failing. The POST to the login endpoint returns HTTP 403 with a non-JSON body (or empty body), which then causes response.json() to raise JSONDecodeError.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.10/dist-packages/webull/webull.py", line 171, in login
    result = response.json()
  File "/usr/lib/python3/dist-packages/requests/models.py", line 900, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/lib/python3.10/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.10/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.10/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

I have printed out the response.

response = requests.post(self._urls.login(), json=data, headers=headers, timeout=self.timeout)
print(response)
result = response.json()

It returned <Response [403]>

Changing the _did parameter did not help.

Could you check please?

apimir avatar Sep 15 '25 17:09 apimir

Same issue here --

nat2k5us avatar Sep 15 '25 18:09 nat2k5us

Looks like all endpoints from endpoints.py doesn't work:

endpoints=(
"https://infoapi.webull.com/api"
"https://quoteapi.webullbroker.com/api"
"https://quotes-gw.webullbroker.com/api"
"https://act.webullbroker.com/webull-paper-center/api"
"https://quoteapi.webullbroker.com/api"
"https://securitiesapi.webullbroker.com/api"
"https://tradeapi.webullbroker.com/api/trade"
"https://userapi.webull.com/api"
"https://userapi.webullbroker.com/api"
"https://ustrade.webullfinance.com/api"
"https://act.webullfintech.com/webull-paper-center/api"
"https://quotes-gw.webullfintech.com/api"
"https://u1suser.webullfintech.com/api"
"https://trade.webullfintech.com/api"
"https://ustrade.webullbroker.com/api"
"https://securitiesapi.webullfintech.com/api"
)

for url in "${endpoints[@]}"; do
    status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url")
    if [ "$status" -eq 200 ]; then
        echo "[OK]     $url"
    else
        echo "[ERROR]  $url (HTTP $status)"
    fi
done
[ERROR]  https://infoapi.webull.com/api (HTTP 503)
[ERROR]  https://quoteapi.webullbroker.com/api (HTTP 503)
[ERROR]  https://quotes-gw.webullbroker.com/api (HTTP 404)
[ERROR]  https://act.webullbroker.com/webull-paper-center/api (HTTP 404)
[ERROR]  https://quoteapi.webullbroker.com/api (HTTP 503)
[ERROR]  https://securitiesapi.webullbroker.com/api (HTTP 503)
[ERROR]  https://tradeapi.webullbroker.com/api/trade (HTTP 404)
[ERROR]  https://userapi.webull.com/api (HTTP 503)
[ERROR]  https://userapi.webullbroker.com/api (HTTP 503)
[ERROR]  https://ustrade.webullfinance.com/api (HTTP 404)
[ERROR]  https://act.webullfintech.com/webull-paper-center/api (HTTP 404)
[ERROR]  https://quotes-gw.webullfintech.com/api (HTTP 404)
[ERROR]  https://u1suser.webullfintech.com/api (HTTP 404)

apimir avatar Sep 15 '25 18:09 apimir

you need to enhance a retry mechanism on authentication, try this , it will work :


# Authentication with retry mechanism
def authenticate_webull(max_retries=3, retry_delay=2):
    """Authenticate with Webull API with retry mechanism"""
    for attempt in range(max_retries):
        try:
            logger.info("Authenticating with Webull API")
            # First check if already logged in
            if wb.is_logged_in():
                logger.info("Already logged in to Webull")
                return True
                
            # Get trade token and login
            wb.get_trade_token(pwd_unlock)
            res = wb.login(wb_email, wb_passcode, 'web', save_token=True)
            
            if wb.is_logged_in():
                logger.info("Successfully authenticated with Webull")
                return True
            else:
                logger.warning(f"Login returned success but is_logged_in check failed (attempt {attempt+1}/{max_retries})")
                
        except Exception as e:
            logger.error(f"Authentication error (attempt {attempt+1}/{max_retries}): {str(e)}")
            
            if attempt < max_retries - 1:
                logger.info(f"Retrying in {retry_delay} seconds...")
                time.sleep(retry_delay)
            else:
                logger.error("All authentication attempts failed")
                return False
    
    return False

henryzhangpku avatar Sep 16 '25 04:09 henryzhangpku

from webull import webull
import os
import sys
import time
import logging
from requests.exceptions import JSONDecodeError

# --- Basic Logging Setup ---
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    stream=sys.stdout,
)

# --- Main Script ---
wb = webull()
username = os.getenv("WB_USERNAME")
password = os.getenv("WB_PASSWORD")

# Print confirmation that credentials are set
if username and password:
    uname_tail = username[-3:] if len(username) >= 3 else username
    logging.info(f"Using WB_USERNAME ending in: ...{uname_tail}")
    logging.info("WB_PASSWORD is set.")
else:
    logging.error("Webull login failed: missing WB_USERNAME or WB_PASSWORD environment variables.")
    sys.exit(1)

def authenticate_webull(max_retries=3, retry_delay=5):
    """Authenticate with Webull API with a robust retry mechanism."""
    for attempt in range(max_retries):
        try:
            logging.info(f"Attempting to log into Webull (Attempt {attempt + 1}/{max_retries})...")
            
            # The login call itself is the primary action.
            response = wb.login(username, password)
            logging.info(f"Login API call successful. Response: {response}")

            # To truly verify, let's try a post-login action.
            logging.info("Verifying login with a data request (get_account)...")
            account_info = wb.get_account()
            if account_info and 'accountMembers' in account_info:
                 logging.info("Successfully authenticated with Webull and verified with account data.")
                 return True
            else:
                logging.warning(f"Login seemed to succeed, but account verification failed. Response: {account_info}")

        except JSONDecodeError as e:
            logging.error(f"Authentication error (attempt {attempt+1}/{max_retries}): The server returned an invalid response (not JSON).")
            logging.error(f"Server Response Text that caused the error: {e.doc}")
            
        except Exception as e:
            logging.error(f"An unexpected authentication error occurred (attempt {attempt+1}/{max_retries}): {str(e)}")
            
        if attempt < max_retries - 1:
            logging.info(f"Retrying in {retry_delay} seconds...")
            time.sleep(retry_delay)
        else:
            logging.error("All authentication attempts failed.")
            return False
    
    return False

# --- Execute the authentication ---
if __name__ == "__main__":
    if authenticate_webull():
        print("\n✅ Webull Login Test Passed!")
        # Example of a next step:
        try:
            quote = wb.get_quote('AAPL')
            print(f"\nSuccessfully fetched a quote for AAPL: {quote.get('name')} is at ${quote.get('close')}")
        except Exception as e:
            print(f"\n⚠️  Could not fetch quote after login: {e}")
    else:
        print("\n❌ Webull Login Test Failed.")
        sys.exit(1)

2025-09-16 07:48:07,014 - INFO - Using WB_USERNAME ending in: ...com 2025-09-16 07:48:07,014 - INFO - WB_PASSWORD is set. 2025-09-16 07:48:07,014 - INFO - Attempting to log into Webull (Attempt 1/3)... 2025-09-16 07:48:07,127 - ERROR - Authentication error (attempt 1/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:07,127 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:07,128 - INFO - Retrying in 5 seconds... 2025-09-16 07:48:12,133 - INFO - Attempting to log into Webull (Attempt 2/3)... 2025-09-16 07:48:12,315 - ERROR - Authentication error (attempt 2/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:12,315 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:12,317 - INFO - Retrying in 5 seconds... 2025-09-16 07:48:17,320 - INFO - Attempting to log into Webull (Attempt 3/3)... 2025-09-16 07:48:17,490 - ERROR - Authentication error (attempt 3/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:17,490 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:17,491 - ERROR - All authentication attempts failed.

❌ Webull Login Test Failed.

nat2k5us avatar Sep 16 '25 12:09 nat2k5us

happening on my end too, retry did not help

DouGrimaldi avatar Sep 16 '25 13:09 DouGrimaldi

your code lacks one important step , you will still need to give did/access_token regularly ,even without this retry:

# Initialize Webull API
wb = webull()
wb._set_did(wb_did)
wb._access_token = wb_access_token

from webull import webull
import os
import sys
import time
import logging
from requests.exceptions import JSONDecodeError

# --- Basic Logging Setup ---
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    stream=sys.stdout,
)

# --- Main Script ---
wb = webull()
username = os.getenv("WB_USERNAME")
password = os.getenv("WB_PASSWORD")

# Print confirmation that credentials are set
if username and password:
    uname_tail = username[-3:] if len(username) >= 3 else username
    logging.info(f"Using WB_USERNAME ending in: ...{uname_tail}")
    logging.info("WB_PASSWORD is set.")
else:
    logging.error("Webull login failed: missing WB_USERNAME or WB_PASSWORD environment variables.")
    sys.exit(1)

def authenticate_webull(max_retries=3, retry_delay=5):
    """Authenticate with Webull API with a robust retry mechanism."""
    for attempt in range(max_retries):
        try:
            logging.info(f"Attempting to log into Webull (Attempt {attempt + 1}/{max_retries})...")
            
            # The login call itself is the primary action.
            response = wb.login(username, password)
            logging.info(f"Login API call successful. Response: {response}")

            # To truly verify, let's try a post-login action.
            logging.info("Verifying login with a data request (get_account)...")
            account_info = wb.get_account()
            if account_info and 'accountMembers' in account_info:
                 logging.info("Successfully authenticated with Webull and verified with account data.")
                 return True
            else:
                logging.warning(f"Login seemed to succeed, but account verification failed. Response: {account_info}")

        except JSONDecodeError as e:
            logging.error(f"Authentication error (attempt {attempt+1}/{max_retries}): The server returned an invalid response (not JSON).")
            logging.error(f"Server Response Text that caused the error: {e.doc}")
            
        except Exception as e:
            logging.error(f"An unexpected authentication error occurred (attempt {attempt+1}/{max_retries}): {str(e)}")
            
        if attempt < max_retries - 1:
            logging.info(f"Retrying in {retry_delay} seconds...")
            time.sleep(retry_delay)
        else:
            logging.error("All authentication attempts failed.")
            return False
    
    return False

# --- Execute the authentication ---
if __name__ == "__main__":
    if authenticate_webull():
        print("\n✅ Webull Login Test Passed!")
        # Example of a next step:
        try:
            quote = wb.get_quote('AAPL')
            print(f"\nSuccessfully fetched a quote for AAPL: {quote.get('name')} is at ${quote.get('close')}")
        except Exception as e:
            print(f"\n⚠️  Could not fetch quote after login: {e}")
    else:
        print("\n❌ Webull Login Test Failed.")
        sys.exit(1)

2025-09-16 07:48:07,014 - INFO - Using WB_USERNAME ending in: ...com 2025-09-16 07:48:07,014 - INFO - WB_PASSWORD is set. 2025-09-16 07:48:07,014 - INFO - Attempting to log into Webull (Attempt 1/3)... 2025-09-16 07:48:07,127 - ERROR - Authentication error (attempt 1/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:07,127 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:07,128 - INFO - Retrying in 5 seconds... 2025-09-16 07:48:12,133 - INFO - Attempting to log into Webull (Attempt 2/3)... 2025-09-16 07:48:12,315 - ERROR - Authentication error (attempt 2/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:12,315 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:12,317 - INFO - Retrying in 5 seconds... 2025-09-16 07:48:17,320 - INFO - Attempting to log into Webull (Attempt 3/3)... 2025-09-16 07:48:17,490 - ERROR - Authentication error (attempt 3/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:17,490 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:17,491 - ERROR - All authentication attempts failed.

❌ Webull Login Test Failed.

henryzhangpku avatar Sep 16 '25 14:09 henryzhangpku

your code lacks one important step , you will still need to give did/access_token regularly ,even without this retry:

# Initialize Webull API
wb = webull()
wb._set_did(wb_did)
wb._access_token = wb_access_token
from webull import webull
import os
import sys
import time
import logging
from requests.exceptions import JSONDecodeError

# --- Basic Logging Setup ---
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    stream=sys.stdout,
)

# --- Main Script ---
wb = webull()
username = os.getenv("WB_USERNAME")
password = os.getenv("WB_PASSWORD")

# Print confirmation that credentials are set
if username and password:
    uname_tail = username[-3:] if len(username) >= 3 else username
    logging.info(f"Using WB_USERNAME ending in: ...{uname_tail}")
    logging.info("WB_PASSWORD is set.")
else:
    logging.error("Webull login failed: missing WB_USERNAME or WB_PASSWORD environment variables.")
    sys.exit(1)

def authenticate_webull(max_retries=3, retry_delay=5):
    """Authenticate with Webull API with a robust retry mechanism."""
    for attempt in range(max_retries):
        try:
            logging.info(f"Attempting to log into Webull (Attempt {attempt + 1}/{max_retries})...")
            
            # The login call itself is the primary action.
            response = wb.login(username, password)
            logging.info(f"Login API call successful. Response: {response}")

            # To truly verify, let's try a post-login action.
            logging.info("Verifying login with a data request (get_account)...")
            account_info = wb.get_account()
            if account_info and 'accountMembers' in account_info:
                 logging.info("Successfully authenticated with Webull and verified with account data.")
                 return True
            else:
                logging.warning(f"Login seemed to succeed, but account verification failed. Response: {account_info}")

        except JSONDecodeError as e:
            logging.error(f"Authentication error (attempt {attempt+1}/{max_retries}): The server returned an invalid response (not JSON).")
            logging.error(f"Server Response Text that caused the error: {e.doc}")
            
        except Exception as e:
            logging.error(f"An unexpected authentication error occurred (attempt {attempt+1}/{max_retries}): {str(e)}")
            
        if attempt < max_retries - 1:
            logging.info(f"Retrying in {retry_delay} seconds...")
            time.sleep(retry_delay)
        else:
            logging.error("All authentication attempts failed.")
            return False
    
    return False

# --- Execute the authentication ---
if __name__ == "__main__":
    if authenticate_webull():
        print("\n✅ Webull Login Test Passed!")
        # Example of a next step:
        try:
            quote = wb.get_quote('AAPL')
            print(f"\nSuccessfully fetched a quote for AAPL: {quote.get('name')} is at ${quote.get('close')}")
        except Exception as e:
            print(f"\n⚠️  Could not fetch quote after login: {e}")
    else:
        print("\n❌ Webull Login Test Failed.")
        sys.exit(1)

2025-09-16 07:48:07,014 - INFO - Using WB_USERNAME ending in: ...com 2025-09-16 07:48:07,014 - INFO - WB_PASSWORD is set. 2025-09-16 07:48:07,014 - INFO - Attempting to log into Webull (Attempt 1/3)... 2025-09-16 07:48:07,127 - ERROR - Authentication error (attempt 1/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:07,127 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:07,128 - INFO - Retrying in 5 seconds... 2025-09-16 07:48:12,133 - INFO - Attempting to log into Webull (Attempt 2/3)... 2025-09-16 07:48:12,315 - ERROR - Authentication error (attempt 2/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:12,315 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:12,317 - INFO - Retrying in 5 seconds... 2025-09-16 07:48:17,320 - INFO - Attempting to log into Webull (Attempt 3/3)... 2025-09-16 07:48:17,490 - ERROR - Authentication error (attempt 3/3): The server returned an invalid response (not JSON). 2025-09-16 07:48:17,490 - ERROR - Server Response Text that caused the error: Illegal Client 2025-09-16 07:48:17,491 - ERROR - All authentication attempts failed. ❌ Webull Login Test Failed.

I still get same error. The link return 503 error

yifancui avatar Sep 16 '25 14:09 yifancui

yep, still 403 error:

2025-09-16 11:31:40,796 - ERROR - Authentication error (attempt 3/3): The server returned an invalid response (not JSON).
2025-09-16 11:31:40,796 - ERROR - Server Response Text that caused the error: Illegal Client
2025-09-16 11:31:40,798 - ERROR - All authentication attempts failed.

I don't know how it worked before, but I think they've added an additional check for some headers in the requests. Possibly related to passport.webull.com

apimir avatar Sep 16 '25 15:09 apimir

I did some viewing of webull request. Here's what I've found for a new header set. x-sv, x-s, and reqid are what im unsure of. x-sv has been constant for me across different chrome instances. Reqid and x-s change every request. From what I can tell, these 2 are important for avoiding a 403. I am not knowledgeable enough about web code and could not find how these were generated

x-sv: a short constant value, consistent between requests, reqid: appears to be string based on 36 its with a length of 32. Changes with every request x-s: appears to be some hashed value. Changes with every request.

self._headers = { # modified 'appid': 'wb_web_passport', 'sec-ch-ua-platform': "Windows", 'device-type': 'Web', 'hl': 'en', 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"', 'sec-ch-ua-mobile': '?0', 'app': 'global', 't_time': str(int(time.time() * 1000)), 'content-type': 'application/json', 'os': 'web', 'platform': 'web', 'osv': 'i9zh', 'did': self._get_did(), "ph": "Windows Chrome", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36", 'ver': '1.0.0', 'app-group': 'broker', 'accept': '/', 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'en-US,en;q=0.9',

        'x-sv': a short constant value, consistent between requests,
        'x-s':  appears to be some hashed value. Changes with every request
        'reqid': appears to be string based on 36 its with a length of 32. Changes with every request

    }

dapigman2003 avatar Sep 16 '25 15:09 dapigman2003

same issue for me since yesterday. Let us know if you find a fix.

emsensing avatar Sep 16 '25 17:09 emsensing

There's no way to fix it unless someone knows how to crack the X_S. Theres a very very obfuscated javascript file within Webull, but I've had a hard time getting anything out of it.

ImNotOssy avatar Sep 16 '25 18:09 ImNotOssy

There's no way to fix it unless someone knows how to crack the X_S. Theres a very very obfuscated javascript file within Webull, but I've had a hard time getting anything out of it.

What information are you trying to get out of X_S?

emsensing avatar Sep 16 '25 18:09 emsensing

There's no way to fix it unless someone knows how to crack the X_S. Theres a very very obfuscated javascript file within Webull, but I've had a hard time getting anything out of it.

What information are you trying to get out of X_S?

It's the key header needed to pass authentication and not get an invalid client error. If we know how to generate it using the other headers, then we can properly log in.

ImNotOssy avatar Sep 16 '25 18:09 ImNotOssy

any update on this?

DouGrimaldi avatar Sep 17 '25 19:09 DouGrimaldi

Webull api is dead now

ImNotOssy avatar Sep 17 '25 19:09 ImNotOssy

If you grab your own web traffic, you can get an access token and refresh token that enables API use again

dapigman2003 avatar Sep 17 '25 23:09 dapigman2003

If you grab your own web traffic, you can get an access token and refresh token that enables API use again

can you give an example

DouGrimaldi avatar Sep 18 '25 00:09 DouGrimaldi

If you grab your own web traffic, you can get an access token and refresh token that enables API use again

can you give an example

I cannot give an actual example because that would allow you to access my account I use mitmweb, grab the request my computer sends to https://u1suser.webullfintech.com/api/user/v1/login/account/v2 In the response to the login is a JSON with a refresh token and access token. You can manually add these to the webull package using the api_login function. The login process is just to get these tokens.

dapigman2003 avatar Sep 18 '25 01:09 dapigman2003

If you grab your own web traffic, you can get an access token and refresh token that enables API use again

can you give an example

I cannot give an actual example because that would allow you to access my account I use mitmweb, grab the request my computer sends to https://u1suser.webullfintech.com/api/user/v1/login/account/v2 In the response to the login is a JSON with a refresh token and access token. You can manually add these to the webull package using the api_login function. The login process is just to get these tokens.

Could you show example in code (don't need set exactly your access token)? And how long live session with access token?

apimir avatar Sep 18 '25 01:09 apimir

hmm maybe i did something wrong but i did find my refresh and access token by using mitmweb and put it in the api_login function but its still giving me Error initializing Webull client: Expecting value: line 1 column 1 (char 0)

DouGrimaldi avatar Sep 18 '25 02:09 DouGrimaldi

Guys , let me clarify : this retry authentication is working perfectly. But you need to refresh/config your did/access_token correctly first , either retry or not . Dm me if you still have issue in authentication.

henryzhangpku avatar Sep 18 '25 02:09 henryzhangpku

hmm maybe i did something wrong but i did find my refresh and access token by using mitmweb and put it in the api_login function but its still giving me Error initializing Webull client: Expecting value: line 1 column 1 (char 0)

You're right. I directly put my token into the webull class item. api_login seems to call some function that breaks. I had originally tested by just adding the token to the init. I think I'll have to modify the package a bit to make this easy to use

Once i load the the token into the webull object, it works fine for my purposes.

dapigman2003 avatar Sep 18 '25 03:09 dapigman2003

If you grab your own web traffic, you can get an access token and refresh token that enables API use again

can you give an example

I cannot give an actual example because that would allow you to access my account I use mitmweb, grab the request my computer sends to https://u1suser.webullfintech.com/api/user/v1/login/account/v2 In the response to the login is a JSON with a refresh token and access token. You can manually add these to the webull package using the api_login function. The login process is just to get these tokens.

Could you show example in code (don't need set exactly your access token)? And how long live session with access token?

I'm not sure what code I can send. I added my token directly to the webull init as the self._refresh_token. someone else pointed out the api_login doesnt actually work

Based on the webull webite response, the access token lasts about a week. My quick google of a refresh token says it should last long term.

dapigman2003 avatar Sep 18 '25 03:09 dapigman2003

I was able to get the login working by manually set the access token, refresh token, uuid, token expire date etc. My question is how often should we manually update this access token? Refresh token doesn't seem to change. I couldn't find token expire date, so I added a random date which actually works fine.

Below is how I updated the 'login' function in webull.py. I am not sure if there is any better way?

    # response = requests.post(self._urls.login(), json=data, headers=headers, timeout=self.timeout)
    # result = response.json()
    # if 'accessToken' in result :
    #     self._access_token = result['accessToken']
    #     self._refresh_token = result['refreshToken']
    #     self._token_expire = result['tokenExpireTime']
    #     self._uuid = result['uuid']
    #     self._account_id = self.get_account_id()
    #     if save_token:
    #         self._save_token(result, token_path)
    
    self._access_token = 'xxxxxxxxxxxxxxx'
    self._refresh_token = 'xxxxxxxxxxxxxxxxxxxx'
    self._token_expire = '2026-02-14T13:35:35.780+0000'
    self._uuid = "xxxxxxxxxxxxxx"
    self._account_id = 'xxxxxxxxxxx'
    result = {
        "accessToken": self._access_token,
        "refreshToken": self._refresh_token,
        "tokenExpireTime":self._token_expire,
        "uuid": self._uuid,
    }

emsensing avatar Sep 18 '25 04:09 emsensing

access_token

Guys , let me clarify : this retry authentication is working perfectly. But you need to refresh/config your did/access_token correctly first , either retry or not . Dm me if you still have issue in authentication.

How often do we need to manually update the access token?

emsensing avatar Sep 18 '25 04:09 emsensing

I was able to get the login working by manually set the access token, refresh token, uuid, token expire date etc. My question is how often should we manually update this access token? Refresh token doesn't seem to change. I couldn't find token expire date, so I added a random date which actually works fine.

Below is how I updated the 'login' function in webull.py. I am not sure if there is any better way?

    # response = requests.post(self._urls.login(), json=data, headers=headers, timeout=self.timeout)
    # result = response.json()
    # if 'accessToken' in result :
    #     self._access_token = result['accessToken']
    #     self._refresh_token = result['refreshToken']
    #     self._token_expire = result['tokenExpireTime']
    #     self._uuid = result['uuid']
    #     self._account_id = self.get_account_id()
    #     if save_token:
    #         self._save_token(result, token_path)
    
    self._access_token = 'xxxxxxxxxxxxxxx'
    self._refresh_token = 'xxxxxxxxxxxxxxxxxxxx'
    self._token_expire = '2026-02-14T13:35:35.780+0000'
    self._uuid = "xxxxxxxxxxxxxx"
    self._account_id = 'xxxxxxxxxxx'
    result = {
        "accessToken": self._access_token,
        "refreshToken": self._refresh_token,
        "tokenExpireTime":self._token_expire,
        "uuid": self._uuid,
    }

this worked thank you so much

DouGrimaldi avatar Sep 18 '25 05:09 DouGrimaldi

by

I was able to get the login working by manually set the access token, refresh token, uuid, token expire date etc. My question is how often should we manually update this access token? Refresh token doesn't seem to change. I couldn't find token expire date, so I added a random date which actually works fine.

Below is how I updated the 'login' function in webull.py. I am not sure if there is any better way?

    # response = requests.post(self._urls.login(), json=data, headers=headers, timeout=self.timeout)
    # result = response.json()
    # if 'accessToken' in result :
    #     self._access_token = result['accessToken']
    #     self._refresh_token = result['refreshToken']
    #     self._token_expire = result['tokenExpireTime']
    #     self._uuid = result['uuid']
    #     self._account_id = self.get_account_id()
    #     if save_token:
    #         self._save_token(result, token_path)
    
    self._access_token = 'xxxxxxxxxxxxxxx'
    self._refresh_token = 'xxxxxxxxxxxxxxxxxxxx'
    self._token_expire = '2026-02-14T13:35:35.780+0000'
    self._uuid = "xxxxxxxxxxxxxx"
    self._account_id = 'xxxxxxxxxxx'
    result = {
        "accessToken": self._access_token,
        "refreshToken": self._refresh_token,
        "tokenExpireTime":self._token_expire,
        "uuid": self._uuid,
    }

by doing this, I can only get the account information, still can't place order. Anybody can advise?

emsensing avatar Sep 19 '25 21:09 emsensing

by

I was able to get the login working by manually set the access token, refresh token, uuid, token expire date etc. My question is how often should we manually update this access token? Refresh token doesn't seem to change. I couldn't find token expire date, so I added a random date which actually works fine. Below is how I updated the 'login' function in webull.py. I am not sure if there is any better way?

    # response = requests.post(self._urls.login(), json=data, headers=headers, timeout=self.timeout)
    # result = response.json()
    # if 'accessToken' in result :
    #     self._access_token = result['accessToken']
    #     self._refresh_token = result['refreshToken']
    #     self._token_expire = result['tokenExpireTime']
    #     self._uuid = result['uuid']
    #     self._account_id = self.get_account_id()
    #     if save_token:
    #         self._save_token(result, token_path)
    
    self._access_token = 'xxxxxxxxxxxxxxx'
    self._refresh_token = 'xxxxxxxxxxxxxxxxxxxx'
    self._token_expire = '2026-02-14T13:35:35.780+0000'
    self._uuid = "xxxxxxxxxxxxxx"
    self._account_id = 'xxxxxxxxxxx'
    result = {
        "accessToken": self._access_token,
        "refreshToken": self._refresh_token,
        "tokenExpireTime":self._token_expire,
        "uuid": self._uuid,
    }

by doing this, I can only get the account information, still can't place order. Anybody can advise?

unfortunately me too i've been trying all day to figure it out, i tried adding x-sv to the library but that didn't work either

DouGrimaldi avatar Sep 19 '25 23:09 DouGrimaldi

access_token

Guys , let me clarify : this retry authentication is working perfectly. But you need to refresh/config your did/access_token correctly first , either retry or not . Dm me if you still have issue in authentication.

How often do we need to manually update the access token?

it is essentially using your web login session. when your PC web session expired or re-login , time to refresh.

henryzhangpku avatar Sep 20 '25 16:09 henryzhangpku

access_token

Guys , let me clarify : this retry authentication is working perfectly. But you need to refresh/config your did/access_token correctly first , either retry or not . Dm me if you still have issue in authentication.

How often do we need to manually update the access token?

it is essentially using your web login session. when your PC web session expired or re-login , time to refresh.

Are you able to submit an order? I can only get account information, not able to place trades.

emsensing avatar Sep 20 '25 16:09 emsensing