WeChat Official Account API Overview
Message Types
WeChat Official Accounts support several messaging paradigms for different use cases.
Mass Messaging
Official accounts can broadcast messages to subscribers at regulated frequencies—daily for subscription accounts and four times monthly for service accounts. Supported formats include text, articles, images, videos, and audio.
Passive Response Messages
When users send messages to an official account, WeChat forwards them to a developer-configured server URL (after security verification). The account must respond within 5 seconds, either with a message or a command indicating no response. Passive responses support encryption, configurable in the developer settings of the official platform.
Customer Service Messages
Within 48 hours of user interaction, official accounts can send unlimited customer service messages. Certain user actions trigger event notifications that enable this capability.
Template Messages
Service notifications such as payment confirmations or appointment reminders can be sent using predefined template formats.
Web-Based Features
OAuth User Authorization
This interface enables retrieval of user profile information through web-based authorization flows.
WeChat JS-SDK
A JavaScript toolkit allowing developers to access native WeChat functionality on web pages, including voice recording/playback, share integration, local image upload, and camera access.
Developer Resources
Official documentation is available at https://mp.weixin.qq.com/wiki/home/index.html
Server Configuration
Connecting to the WeChat platform requires three steps:
- Configure server settings
- Verify URL validity
- Implement business logic
Server Configuration Setup
After logging into the WeChat backend, navigate to Developer Center and click "Modify Configuration" to enter:
- URL: The endpoint receiving WeChat messages and events
- Token: Arbitrary string used for signature verification
- EncodingAESKey: Manual or auto-generated key for message encryption
Three encryption modes are available: Plaintext (default), Compatible, and Secure. Switching to Compatible or Secure mode requires implementing corresponding encryption logic.
Note: WeChat Official Account APIs require port 80.
Test Environment
Sandbox testing available at: http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
Nginx Reverse Proxy Configuration
Configure nginx to forward requests:
location /weixin {
proxy_pass http://127.0.0.1:8000;
}
Access the endpoint at: http://YOUR_SERVER_IP/weixin
URL Validation
Upon configuration submission, WeChat sends a GET request with four parameters:
Developers verify the request by checking the signature. Return the echostr parameter unchanged to confirm successful verification.
Verification Algorithm:
- Sort
token,timestamp, andnoncelexicographically - Concatenate into a single string and compute SHA1 hash
- Compare the computed signature with the provided
signatureparameter
Flask Implementation:
# -*- coding:utf-8 -*-
from flask import Flask, request, make_response
import hashlib
import xmltodict
import time
app = Flask(__name__)
WECHAT_TOKEN = "my_secret_token"
@app.route('/weixin', methods=['GET', 'POST'])
def wechat():
args = request.args
signature = args.get('signature')
timestamp = args.get('timestamp')
nonce = args.get('nonce')
echostr = args.get('echostr')
# Step 1: Lexicographic sort of parameters
temp = [WECHAT_TOKEN, timestamp, nonce]
temp.sort()
# Step 2: SHA1 hash of concatenated string
temp = "".join(temp)
computed_sig = hashlib.sha1(temp).hexdigest()
# Step 3: Verify signature matches
if computed_sig == signature:
if request.method == "GET":
return echostr
else:
# Handle incoming messages
pass
else:
return 'errno', 403
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Receiving and Sending Messages
After successful URL verification, the account becomes operational. Service accounts (subscription accounts have limited capabilities) can receive user messages and menu interaction events at the configured endpoint.
Messages from users arrive with an OpenID identifier—a unique hash per user-account combination.
Receiving Regular Messages
WeChat forwards user messages via POST requests containing XML payloads. The server must respond within 5 seconds; otherwise, WeChat retries up to three times. For longer processing, return an empty response to prevent retries.
Example XML Message:
<xml>
<ToUserName><![CDATA[gh_866835093fea]]></ToUserName>
<FromUserName><![CDATA[ogdotwSc_MmEEsJs9-ABZ1QL_4r4]]></FromUserName>
<CreateTime>1478317060</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[Hello]]></Content>
<MsgId>6349323426230210995</MsgId>
</xml>
Note: <![CDATA[ and ]]> sections bypass XML parsing.
Working with XML Data
The xmltodict library simplifies XML handling in Python.
Converting XML to Dictionary
>>> import xmltodict
>>> xml_str = """
... <xml>
... <ToUserName><![CDATA[gh_866835093fea]]></ToUserName>
... <FromUserName><![CDATA[ogdotwSc_MmEEsJs9-ABZ1QL_4r4]]></FromUserName>
... <CreateTime>1478317060</CreateTime>
... <MsgType><![CDATA[text]]></MsgType>
... <Content><![CDATA[Hello]]></Content>
... <MsgId>6349323426230210995</MsgId>
... </xml>
... """
>>> xml_dict = xmltodict.parse(xml_str)
>>> xml_dict['xml']['Content']
'Hello'
Converting Dictionary to XML
xml_dict = {
"xml": {
"ToUserName": "gh_866835093fea",
"FromUserName": "ogdotwSc_MmEEsJs9-ABZ1QL_4r4",
"CreateTime": "1478317060",
"MsgType": "text",
"Content": "Hello",
"MsgId": "6349323426230210995",
}
}
xml_str = xmltodict.unparse(xml_dict, pretty=True)
print(xml_str)
Message Categories
- Text messages
- Image messages
- Voice messages
- Video messages
- Short video messages
- Location messages
- Link messages
Text Message Format:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[Content here]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
Passive Response Messages
When responding to user messages, return specific XML structures within 5 seconds. If processing takes longer:
- Return the string "success" (recommended)
- Return an empty string
Failing to respond or returning invalid data triggers a user-facing error message.
Response Message Types
- Text
- Image
- Voice
- Video
- Music
- News (图文)
Text Response Format:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[Response content]]></Content>
</xml>
Echo Bot Implementation
The following example implements an echo bot that reflects text messages back to users:
# -*- coding:utf-8 -*-
from flask import Flask, request, make_response
import hashlib
import xmltodict
import time
app = Flask(__name__)
WECHAT_TOKEN = "my_secret_token"
@app.route('/weixin', methods=['GET', 'POST'])
def wechat():
args = request.args
signature = args.get('signature')
timestamp = args.get('timestamp')
nonce = args.get('nonce')
echostr = args.get('echostr')
temp = [WECHAT_TOKEN, timestamp, nonce]
temp.sort()
temp = "".join(temp)
computed_sig = hashlib.sha1(temp).hexdigest()
if computed_sig == signature:
if request.method == "GET":
return echostr
else:
raw_data = request.data
parsed = xmltodict.parse(raw_data).get('xml')
if 'text' == parsed.get('MsgType'):
response = {
"ToUserName": parsed.get('FromUserName'),
"FromUserName": parsed.get('ToUserName'),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": parsed.get('Content'),
}
else:
response = {
"ToUserName": parsed.get('FromUserName'),
"FromUserName": parsed.get('ToUserName'),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "Unsupported message type",
}
if response:
response = {"xml": response}
response = xmltodict.unparse(response)
else:
response = ''
return make_response(response)
else:
return 'errno', 403
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Emoji and Special Characters
QQ Emoticons
These are escaped text sequences like /::D or /::P, treated as text messages.
Emoji
Emoji originated from Japanese mobile carriers, designed by Shigetaka Kurita for NTT DoCoMo's i-mode system. They use Unicode ranges E63E to E757. Since iOS 5 added emoji support, they've become globally standardized through Unicode adoption.
Emoji are Unicode characters and are processed as text messages.
Custom Stickers
WeChat custom stickers are neither text nor images—they lack official API support for handling.
Other Message Types
Image Message:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<PicUrl><![CDATA[picture_url]]></PicUrl>
<MediaId><![CDATA[media_id]]></MediaId>
<MsgId>1234567890123456</MsgId>
</xml>
| Parameter | Description |
|---|---|
| ToUserName | Official account ID |
| FromUserName | Sender's OpenID |
| CreateTime | Message creation timestamp |
| MsgType | Message type (image) |
| PicUrl | Image URL |
| MediaId | Media ID for retrieval |
| MsgId | Unique message identifier |
Video Message:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<ThumbMediaId><![CDATA[thumb_id]]></ThumbMediaId>
<MsgId>1234567890123456</MsgId>
</xml>
Short Video Message:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[shortvideo]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<ThumbMediaId><![CDATA[thumb_id]]></ThumbMediaId>
<MsgId>1234567890123456</MsgId>
</xml>
Voice Message:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<Format><![CDATA[amr]]></Format>
<MsgId>1234567890123456</MsgId>
</xml>
When voice recognition is enabled, a Recognition field appears:
<Recognition><![CDATA[Transcribed text]]></Recognition>
Format indicates the audio codec (typically amr), and Recognition contains UTF-8 transcribed text.
Response Formats for Media Messages
Image Response:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[media_id]]></MediaId>
</Image>
</xml>
Video Response:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<Video>
<MediaId><![CDATA[media_id]]></MediaId>
<Title><![CDATA[Title]]></Title>
<Description><![CDATA[Description]]></Description>
</Video>
</xml>
Voice Recognition Response
Converting voice messages to text:
# -*- coding:utf-8 -*-
from flask import Flask, request, make_response
import hashlib
import xmltodict
import time
app = Flask(__name__)
WECHAT_TOKEN = "my_secret_token"
@app.route('/weixin', methods=['GET', 'POST'])
def wechat():
args = request.args
signature = args.get('signature')
timestamp = args.get('timestamp')
nonce = args.get('nonce')
echostr = args.get('echostr')
temp = [WECHAT_TOKEN, timestamp, nonce]
temp.sort()
temp = "".join(temp)
computed_sig = hashlib.sha1(temp).hexdigest()
if computed_sig == signature:
if request.method == "GET":
return echostr
else:
raw_data = request.data
parsed = xmltodict.parse(raw_data).get('xml')
if 'voice' == parsed.get('MsgType'):
recognized_text = parsed.get('Recognition') or 'Could not recognize'
response = {
"ToUserName": parsed.get('FromUserName'),
"FromUserName": parsed.get('ToUserName'),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": recognized_text
}
else:
response = {
"ToUserName": parsed.get('FromUserName'),
"FromUserName": parsed.get('ToUserName'),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "Send me a voice message!",
}
if response:
response = {"xml": response}
response = xmltodict.unparse(response)
else:
response = ''
return make_response(response)
else:
return 'errno', 403
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Subscribe/Unsubscribe Events
When users subscribe or unsubscribe, WeChat pushes events to the configured URL. Same retry logic applies (3 attempts, 5-second timeout).
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>
Welcome Message on Subscribe:
# -*- coding:utf-8 -*-
from flask import Flask, request, make_response
import hashlib
import xmltodict
import time
app = Flask(__name__)
WECHAT_TOKEN = "my_secret_token"
@app.route('/weixin', methods=['GET', 'POST'])
def wechat():
args = request.args
signature = args.get('signature')
timestamp = args.get('timestamp')
nonce = args.get('nonce')
echostr = args.get('echostr')
temp = [WECHAT_TOKEN, timestamp, nonce]
temp.sort()
temp = "".join(temp)
computed_sig = hashlib.sha1(temp).hexdigest()
if computed_sig == signature:
if request.method == "GET":
return echostr
else:
raw_data = request.data
parsed = xmltodict.parse(raw_data).get('xml')
if "event" == parsed.get('MsgType'):
if "subscribe" == parsed.get("Event"):
response = {
"ToUserName": parsed.get("FromUserName", ""),
"FromUserName": parsed.get("ToUserName", ""),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "Thanks for following us!"
}
else:
response = None
else:
response = {
"ToUserName": parsed.get('FromUserName'),
"FromUserName": parsed.get('ToUserName'),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "Hello!",
}
if response:
response = {"xml": response}
response = xmltodict.unparse(response)
else:
response = ''
return make_response(response)
else:
return 'errno', 403
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Access Token Management
The access_token is a globally unique credential required for all API calls. It must be stored with atleast 512 characters of space and currently expires after 2 hours, requiring periodic refresh.
API Endpoint
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
Error Response
{
"errcode": 40013,
"errmsg": "invalid appid"
}
Token Manager Implementation
# -*- coding:utf-8 -*-
import time
import urllib2
import json
from flask import Flask, request
WECHAT_APPID = "your_app_id"
WECHAT_APPSECRET = "your_app_secret"
class AccessToken(object):
"""
Handles access token retrieval, storage, and expiration checking
"""
_token_cache = {
"access_token": "",
"update_time": time.time(),
"expires_in": 7200
}
@classmethod
def get_access_token(cls):
if not cls._token_cache.get('access_token') or (
time.time() - cls._token_cache.get('update_time')) > cls._token_cache.get('expires_in'):
url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s' % (
WECHAT_APPID, WECHAT_APPSECRET)
response = urllib2.urlopen(url).read()
resp_json = json.loads(response)
if 'errcode' in resp_json:
raise Exception(resp_json.get('errmsg'))
else:
cls._token_cache['access_token'] = resp_json.get('access_token')
cls._token_cache['expires_in'] = resp_json.get('expires_in')
cls._token_cache['update_time'] = time.time()
return cls._token_cache.get('access_token')
else:
return cls._token_cache.get('access_token')
if __name__ == '__main__':
print(AccessToken.get_access_token())
Parameterized QR Codes
Generate QR codes with embedded scene parameters for campaign tracking and account binding scenarios.
Two types available:
- Temporary QR Codes: Expire after a configurable period (max 30 days), support larger quantities
- Permanent QR Codes: No expiration but limited quantity (100,000 maximum)
Process involves creating a ticket, then exchanging it for the actual QR image.
Creating QR Code Tickets
Temporary QR Code Request:
POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
{
"expire_seconds": 604800,
"action_name": "QR_SCENE",
"action_info": {
"scene": {
"scene_id": 123
}
}
}
Permanent QR Code Request:
POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
{
"action_name": "QR_LIMIT_SCENE",
"action_info": {
"scene": {
"scene_id": 123
}
}
}
Or using string parameters:
{"action_name": "QR_LIMIT_STR_SCENE", "action_info": {"scene": {"scene_str": "parameter"}}}
Success Response:
{
"ticket": "gQH47joAAAAAAAAAASx...",
"expire_seconds": 60,
"url": "http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"
}
Error Response:
{"errcode": 40013, "errmsg": "invalid appid"}
Retrieving QR Code Image
GET https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
QR Code Generator Implementation
# -*- coding:utf-8 -*-
import time
import urllib2
import json
from flask import Flask, request
WECHAT_APPID = "your_app_id"
WECHAT_APPSECRET = "your_app_secret"
class AccessToken(object):
_token_cache = {
"access_token": "",
"update_time": time.time(),
"expires_in": 7200
}
@classmethod
def get_access_token(cls):
if not cls._token_cache.get('access_token') or (
time.time() - cls._token_cache.get('update_time')) > cls._token_cache.get('expires_in'):
url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s' % (
WECHAT_APPID, WECHAT_APPSECRET)
response = urllib2.urlopen(url).read()
resp_json = json.loads(response)
if 'errcode' in resp_json:
raise Exception(resp_json.get('errmsg'))
else:
cls._token_cache['access_token'] = resp_json.get('access_token')
cls._token_cache['expires_in'] = resp_json.get('expires_in')
cls._token_cache['update_time'] = time.time()
return cls._token_cache.get('access_token')
else:
return cls._token_cache.get('access_token')
app = Flask(__name__)
@app.route('/get_qrcode')
def get_qrcode():
scene_id = request.args.get('id')
access_token = AccessToken.get_access_token()
url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s" % access_token
params = {
"expire_seconds": 604800,
"action_name": "QR_SCENE",
"action_info": {"scene": {"scene_id": scene_id}}
}
response = urllib2.urlopen(url, data=json.dumps(params)).read()
resp_json = json.loads(response)
ticket = resp_json.get('ticket')
if ticket:
return '<img src="https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s">' % ticket
else:
return resp_json
if __name__ == '__main__':
app.run(host='0.0.0.0')
Scanning Parameterized QR Codes
Scanning a parameterized QR code triggers different events based on subscription status:
- New followers: Subscribe event with scene parameter pushed to server
- Existing followers: Scan event with scene parameter pushed to server
New Subscriber Event
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[qrscene_123123]]></EventKey>
<Ticket><![CDATA[TICKET]]></Ticket>
</xml>
| Parameter | Description |
|---|---|
| ToUserName | Official account ID |
| FromUserName | Follower's OpenID |
| CreateTime | Event timestamp |
| MsgType | Always "event" |
| Event | "subscribe" for new followers |
| EventKey | Scene value prefixed with "qrscene_" |
| Ticket | QR code ticket |
Existing Subscriber Scan Event
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[SCAN]]></Event>
<EventKey><![CDATA[SCENE_VALUE]]></EventKey>
<Ticket><![CDATA[TICKET]]></Ticket>
</xml>
| Parameter | Description |
|---|---|
| EventKey | 32-bit unsigned scene_id from QR creation |
| Event | "SCAN" for existing followers |
Scene-Aware Event Handler
# -*- coding:utf-8 -*-
from flask import Flask, request, make_response
import hashlib
import xmltodict
import time
app = Flask(__name__)
WECHAT_TOKEN = "my_secret_token"
@app.route('/weixin', methods=['GET', 'POST'])
def wechat():
args = request.args
signature = args.get('signature')
timestamp = args.get('timestamp')
nonce = args.get('nonce')
echostr = args.get('echostr')
temp = [WECHAT_TOKEN, timestamp, nonce]
temp.sort()
temp = "".join(temp)
computed_sig = hashlib.sha1(temp).hexdigest()
if computed_sig == signature:
if request.method == "GET":
return echostr
else:
raw_data = request.data
parsed = xmltodict.parse(raw_data).get('xml')
if 'text' == parsed.get('MsgType'):
response = {
"ToUserName": parsed.get('FromUserName'),
"FromUserName": parsed.get('ToUserName'),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": parsed.get('Content'),
}
elif "event" == parsed.get('MsgType'):
if "subscribe" == parsed.get("Event"):
response = {
"ToUserName": parsed.get("FromUserName", ""),
"FromUserName": parsed.get("ToUserName", ""),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "Thanks for following!"
}
if parsed.get('EventKey'):
response["Content"] += " Scene: " + parsed.get('EventKey')
elif 'SCAN' == parsed.get('Event'):
response = {
"ToUserName": parsed.get("FromUserName", ""),
"FromUserName": parsed.get("ToUserName", ""),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": parsed.get('EventKey')
}
else:
response = None
else:
response = {
"ToUserName": parsed.get('FromUserName'),
"FromUserName": parsed.get('ToUserName'),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "Hello!",
}
if response:
response = {"xml": response}
response = xmltodict.unparse(response)
else:
response = ''
return make_response(response)
else:
return 'errno', 403
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Custom Menu Management
# -*- coding: utf-8 -*-
import urllib
from token_manager import AccessToken
class MenuManager(object):
def __init__(self):
pass
def create(self, post_data, access_token):
post_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s" % access_token
if isinstance(post_data, unicode):
post_data = post_data.encode('utf-8')
response = urllib.urlopen(url=post_url, data=post_data)
print(response.read())
def query(self, access_token):
post_url = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=%s" % access_token
response = urllib.urlopen(url=post_url)
print(response.read())
def delete(self, access_token):
post_url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=%s" % access_token
response = urllib.urlopen(url=post_url)
print(response.read())
def get_current_info(self, access_token):
post_url = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=%s" % access_token
response = urllib.urlopen(url=post_url)
print(response.read())
if __name__ == '__main__':
manager = MenuManager()
menu_json = '''
{
"button": [
{
"type": "click",
"name": "Profile",
"key": "profile"
},
{
"type": "click",
"name": "History",
"key": "history"
},
{
"type": "click",
"name": "Contact Us",
"key": "contact"
}
]
}
'''
submenu_json = '''
{
"button": [
{
"type": "click",
"name": "Today's Music",
"key": "V1001_TODAY_MUSIC"
},
{
"name": "Menu",
"sub_button": [
{
"type": "view",
"name": "Search",
"url": "http://www.soso.com/"
},
{
"type": "view",
"name": "Blog",
"url": "http://www.example.com/"
}
]
}
]
}
'''
access_token = AccessToken.get_access_token()
manager.create(submenu_json, access_token)