{"id":83,"library":"wechatpy","title":"WeChat Mini Program Server API","description":"Server-side Python SDK for WeChat (Weixin) Mini Programs. Handles the backend half of Mini Program interactions: login verification (jscode2session), access token management, template messages, and encrypted data decryption. The dominant community SDK is wechatpy — no official Tencent Python SDK exists. Mini Program frontend runs in WeChat's JavaScript runtime (WXML/WXSS); Python handles server-side API calls only.","status":"active","version":"1.8.18","language":"python","source_language":"zh","source_url":"https://developers.weixin.qq.com/miniprogram/en/dev/framework/server-ability/backend-api.html","tags":["wechat","miniprogram","weixin","china","tencent","login","openid","session","mobile-payment"],"install":[{"cmd":"pip install 'wechatpy[cryptography]'","lang":"bash","label":"Recommended (with AES decryption support)"},{"cmd":"pip install wechatpy","lang":"bash","label":"Base install (no encrypted data decryption)"}],"dependencies":[{"reason":"AES-128-CBC decryption for encrypted user data (phone, userInfo)","package":"cryptography","optional":true},{"reason":"HTTP client for WeChat API calls","package":"requests","optional":false},{"reason":"XML parsing for WeChat message payloads","package":"xmltodict","optional":false}],"imports":[{"note":"WeChatClient is for Official Accounts (MP), not Mini Programs — different API surface.","wrong":"from wechatpy import WeChatClient","symbol":"WeChatMiniProgram","correct":"from wechatpy.miniprogram import WeChatMiniProgram"}],"quickstart":{"code":"from wechatpy.miniprogram import WeChatMiniProgram\nimport requests\n\nAPPID = 'your_appid'\nSECRET = 'your_appsecret'\n\n# Initialize client\nclient = WeChatMiniProgram(APPID, SECRET)\n\n# Step 1: Exchange wx.login() code for openid + session_key\n# Frontend calls wx.login() and sends the code to your server\ndef login(js_code):\n    result = client.code_to_session(js_code)\n    # result = {'openid': '...', 'session_key': '...', 'unionid': '...' (if linked)}\n    openid = result['openid']\n    session_key = result['session_key']\n    # NEVER send session_key to frontend — generate your own custom token\n    return openid, session_key\n\n# Step 2: Get phone number (NEW method, base library >= 2.21.2)\n# Frontend button fires bindgetphonenumber event, returns e.detail.code\n# Send that code to your server:\ndef get_phone_number(phone_code):\n    # phone_code is different from wx.login() code — do NOT mix them\n    resp = requests.post(\n        'https://api.weixin.qq.com/wxa/business/getuserphonenumber',\n        params={'access_token': client.access_token},\n        json={'code': phone_code}\n    )\n    data = resp.json()\n    return data['phone_info']['phoneNumber']  # e.g. '+8613800138000'\n\n# Step 3: Global access_token (cache this — 2hr TTL, shared across all users)\nprint(client.access_token)  # auto-fetched and cached by wechatpy","lang":"python","description":"Use WeChatMiniProgram (not WeChatClient) for Mini Programs. Cache access_token in Redis across server instances — it has a 2hr TTL and a 2000 calls/day refresh limit. Never send session_key to the frontend."},"warnings":[{"fix":"Use the code-exchange pattern. Never mix wx.login() code with getPhoneNumber code. Old AES decrypt pattern is in 90% of tutorials but deprecated.","message":"Phone number retrieval API changed in base library 2.21.2. Old pattern (encryptedData + iv AES decrypt using session_key) still works but is deprecated. New pattern: frontend button callback returns a `code`, server exchanges it via POST /wxa/business/getuserphonenumber. The two codes (wx.login code vs phone code) are NOT interchangeable.","severity":"breaking","affected_versions":"all"},{"fix":"Pre-purchase call quota at mp.weixin.qq.com → Payment Management before going live.","message":"getPhoneNumber and getRealtimePhoneNumber components are paid since August 28, 2023. Standard price: ¥0.03/call (quick verification) and ¥0.04/call (real-time verification). Each Mini Program account gets 1000 free calls for development. Production usage requires prepaid balance on WeChat Open Platform.","severity":"breaking","affected_versions":"all"},{"fix":"Always use .get('unionid') not ['unionid']. Bind your Mini Program to an Open Platform account if cross-app user identity is needed.","message":"`unionid` is NOT always returned by jscode2session. It only appears if the Mini Program is bound to a WeChat Open Platform account AND the user has authorized it. Many tutorials assume unionid is always present, causing KeyError in production.","severity":"breaking","affected_versions":"all"},{"fix":"Store access_token in Redis or a shared cache and check expiry before refreshing. Use wechatpy's session storage: WeChatMiniProgram(appid, secret, session=RedisStorage(redis_client)).","message":"access_token is a global credential with a 2-hour TTL and a 2000 calls/day refresh limit. Calling getAccessToken on every request will exhaust the daily quota and lock out all users. wechatpy caches it in-process but does NOT share across multiple server instances.","severity":"breaking","affected_versions":"all"},{"fix":"Generate your own opaque session token (e.g. UUID) and map it to openid + session_key server-side.","message":"session_key must NEVER be sent to the frontend or logged. It is used server-side to decrypt sensitive user data. If session_key leaks, attackers can decrypt all encrypted user data. WeChat will invalidate session_key on wx.login() re-call.","severity":"gotcha","affected_versions":"all"},{"fix":"Requires Chinese business entity + WeChat verification. Overseas businesses must use a Chinese registered subsidiary or partner.","message":"Phone number API requires Mini Program registered as non-individual entity with WeChat verification. Individual developer accounts (个人开发者) cannot access getPhoneNumber even with balance. Overseas entities (non-China business registration) are also blocked.","severity":"gotcha","affected_versions":"all"},{"fix":"For newer APIs not in wechatpy, call the WeChat API directly via requests using client.access_token.","message":"wechatpy v1.8.18 is the last PyPI release and the package is effectively in maintenance mode (Snyk flags it as inactive). Core APIs remain functional but new WeChat features may not be supported.","severity":"gotcha","affected_versions":"wechatpy==1.8.18"},{"fix":"from wechatpy.miniprogram import WeChatMiniProgram — not WeChatClient.","message":"WeChatClient (for Official Accounts/MP) and WeChatMiniProgram (for Mini Programs) are different classes with different API surfaces. Official Account tutorials are the majority online — importing the wrong client causes silent failures or wrong endpoint calls.","severity":"gotcha","affected_versions":"all"},{"fix":"Ensure the Python interpreter running the script has `wechatpy` installed and is properly configured to access it (e.g., using a virtual environment). Try reinstalling `wechatpy` in a clean virtual environment. Verify the contents of the installed `wechatpy` package to confirm `wechatpy/miniprogram/__init__.py` exists.","message":"The `wechatpy.miniprogram` module, which contains `WeChatMiniProgram`, is fundamental for Mini Program development. A `ModuleNotFoundError` for this module, despite `wechatpy` (e.g., version 1.8.18) being reported as successfully installed, indicates a problem with the Python environment where the package was installed versus where the script is executed, or a corrupted installation.","severity":"breaking","affected_versions":"wechatpy==1.8.18"},{"fix":"Use an officially supported Python version (e.g., Python 3.8-3.11) with wechatpy-1.8.18, or consider migrating to a more actively maintained WeChat SDK for Python 3.13.","message":"wechatpy-1.8.18, despite installing successfully, fails to import 'wechatpy.miniprogram' on Python 3.13 environments (e.g., python:3.13-alpine). This indicates a potential incompatibility or packaging issue with newer Python versions, as wechatpy is in maintenance mode and may not support Python 3.13.","severity":"breaking","affected_versions":"wechatpy==1.8.18"}],"env_vars":null,"last_verified":"2026-05-12T07:55:41.949Z","next_check":"2026-06-01T00:00:00.000Z","problems":[{"fix":"Ensure the `session_key` and `iv` obtained from the `jscode2session` API are correctly passed to the decryption function, and that the `encrypted_data` is the exact string received from the Mini Program frontend without alteration.","cause":"This error often occurs during encrypted data decryption (e.g., Mini Program user data) if the session key, IV, or encrypted data is corrupted, malformed, or does not match the expected format.","error":"ValueError: Padding is incorrect and cannot be removed."},{"fix":"Verify that your WeChat Mini Program's `AppID` and `AppSecret` are correctly configured in your `WeChatClient` or `MiniProgramClient` initialization, and that they are up-to-date and valid.","cause":"This is a WeChat API response indicating that the `appid` or `appsecret` used in your `wechatpy` client initialization is incorrect, expired, or does not have the necessary permissions for the requested operation.","error":"{\"errcode\":40001,\"errmsg\":\"invalid credential\"}"},{"fix":"Check that the WeChat 'Token' configured in your Mini Program/Official Account backend developer settings is exactly the same as the `token` parameter used when initializing your `WeChatClient` or `WeChatServer`.","cause":"This exception is raised when `wechatpy` fails to verify the signature of an incoming WeChat message or event, meaning the token configured in your WeChat developer console does not match the token used by your `wechatpy` application.","error":"wechatpy.exceptions.InvalidSignatureException"},{"fix":"Ensure the JS Code sent from the Mini Program frontend is fresh (generated just before the request) and used only once per `jscode2session` call. It expires very quickly.","cause":"This WeChat API response specifically for `jscode2session` indicates that the JS Code provided by the Mini Program frontend is invalid, expired, or has already been used to exchange for a session key.","error":"{\"errcode\":40029,\"errmsg\":\"invalid code\"}"}],"ecosystem":"pypi","meta_description":null,"install_score":0,"install_tag":"stale","quickstart_score":0,"quickstart_tag":"stale","pypi_latest":null,"install_checks":{"last_tested":"2026-05-12","tag":"stale","tag_description":"widespread failures or data too old to trust","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"cryptography","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null}]},"quickstart_checks":{"last_tested":"2026-05-12","tag":"stale","tag_description":"widespread failures or data too old to trust","results":[{"runtime":"python:3.10-alpine","exit_code":1},{"runtime":"python:3.10-slim","exit_code":1},{"runtime":"python:3.11-alpine","exit_code":1},{"runtime":"python:3.11-slim","exit_code":1},{"runtime":"python:3.12-alpine","exit_code":1},{"runtime":"python:3.12-slim","exit_code":1},{"runtime":"python:3.13-alpine","exit_code":1},{"runtime":"python:3.13-slim","exit_code":1},{"runtime":"python:3.9-alpine","exit_code":1},{"runtime":"python:3.9-slim","exit_code":1}]}}