How to Build a WeChat Bridge API for Login Using Node.js & TypeScript

This guide explains how to create a bridge API that enables login and message interaction with WeChat by reverse‑engineering client and server protocols, covering UUID acquisition, QR code handling, login status polling, cookie management, and data initialization using Node.js, TypeScript, and Axios.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
How to Build a WeChat Bridge API for Login Using Node.js & TypeScript

Preface

WeChat is a widely used social tool that has penetrated many aspects of daily life, from work communication to customer relationship management and commercial conversion. Various software applications have emerged to assist in managing WeChat, retrieving friend and group data, and automatically sending messages.

Popular examples include a ChatGPT‑based WeChat bot that enables interactive Q&A, and SCRM platforms that customize SOP flows for precise community operations. However, WeChat does not provide any official SDK or API, leaving developers to bridge the gap themselves. This article introduces how to build such a bridge API.

Solution Overview

The bridge API can be implemented in two main ways: client‑side interaction or direct server‑side interaction with WeChat’s protocol.

Client‑Side Interaction

WeChat offers multiple clients (Android, iOS, PC, Web). The bridge API must be able to read or intercept messages from the client and send messages through the client. For mobile clients this typically requires jailbreaking/rooting and hooking functions (Android uses Xposed/LSPosed, iOS uses Cydia/Cycript). The author notes limited knowledge of mobile development, so the description is speculative.

The Web version can be automated with headless browsers like Puppeteer: open the Web WeChat page, inject scripts, and control message sending, which is cheaper than mobile solutions.

Server‑Side Interaction

If the WeChat interface can be obtained, the bridge can directly call the API. Accessing the mobile interface is difficult, but the Web interface’s HTTPS endpoints are visible and easier to use. This method is more direct but risks the endpoints being shut down. The article focuses on this approach, starting with the login process.

Login Flow

The Web WeChat login consists of four steps:

Step 1: The client requests a UUID from the server, which is used as a unique identifier, then generates a QR‑code image and continuously polls the UUID status (Wait).

Step 2: The user scans the QR‑code; the status changes to Scaned.

Step 3: The user confirms login; the status changes to Success.

Step 4: After successful login, the client initializes user data, obtains personal information and a series of cookies, and finally requests the user's WeChat data (including group info).

Login API

1.0 Get UUID

Endpoint: https://login.wx.qq.com/jslogin (GET)

Parameters:

appid = wx782c26e4c19acffb (fixed)

fun = new (fixed)

lang = zh_CN _ = current timestamp

redirect_uri = fixed URL

Response example:

window.QRLogin.code = 200; window.QRLogin.uuid = "xxxxxxxxxx==";

The value after window.QRLogin.uuid is the UUID.

2.0 Get QR Code

Endpoint: https://login.wx.qq.com/qrcode/{uuid} (GET)

Parameter:

t = webwx (fixed)

Response: binary image of the QR code.

2.1 Poll Login Status

Endpoint: https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login (GET)

Parameters:

tip = 1 (not scanned) or 0 (scanned)

uuid = the UUID obtained earlier

_ = current timestamp

loginicon = true Response example:

window.code=200;window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=...&uuid=xxxxxxxxxx==&lang=zh_CN&scan=1671383680";
window.code

values: 408 Wait, 201 Scanned, 200 Success. redirect_uri is used to obtain login information.

3.0 Get Login Information

Endpoint: the redirect_uri returned by step 2.1 (e.g.,

https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=...&uuid=...&lang=zh_CN&scan=...

) (GET)

Additional parameters:

fun = new version = v2 Response (XML) contains skey, wxsid, wxuin, and pass_ticket, which are essential for subsequent requests.

<error>
  <ret>0</ret>
  <message></message>
  <skey>@crypt_abcd_abdftyzekmxkk</skey>
  <wxsid>abcdefghi</wxsid>
  <wxuin>987432189</wxuin>
  <pass_ticket>wawvwjokljhaax</pass_ticket>
  <isgrayscale>1</isgrayscale>
</error>

4.0 Get WeChat Data

Endpoint:

https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?pass_ticket={pass_ticket}

(POST)

POST body (JSON) must include a BaseRequest object with Uin, Sid, Skey, and DeviceID. The request must also carry the cookies obtained in step 3.

{
  "BaseRequest": {
    "Uin": "{wxuin}",
    "Sid": "{wxsid}",
    "Skey": "{skey}",
    "DeviceID": "e123456789012345"
  }
}

The response is a large JSON containing friends, groups, and other data.

Bridge API Implementation (Node.js & TypeScript)

The following TypeScript code defines enums, interfaces, and constants used throughout the login process.

export enum LoginMode {
  Normal = 'normal',
  Desktop = 'desktop',
}

export enum LoginStatus {
  Success = "200",
  Scanned = "201",
  Timeout = "400",
  Wait    = "408",
  Error   = ''
}

export interface Login { status: LoginStatus; redirect: string; }

export const wxApp = {
  appId: "wx782c26e4c19acffb",
  appMessageAppId: "wxeb7ec651dd0aefa9",
  jsonContentType: "application/json; charset=utf-8",
  uosPatchClientVersion: "2.0.0",
  uosPatchExtspam: "Go8FCIkFEokFCggwMDAwMDAwMRAGGvAESySibk50w5Wb3uTl2h64jVVrV7gNs06GFlWplHQbY/5FfiO++1yH4ykC...",
  useAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
};

export const wxApi = {
  webwxnewloginpage: "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage",
  jslogin: "https://login.wx.qq.com/jslogin",
  login: "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login",
  qrcode: "https://login.weixin.qq.com/qrcode",
  qrcodeLogin: "https://login.weixin.qq.com/l",
  webwxinit: "/cgi-bin/mmwebwx-bin/webwxinit",
};

export const wxOption = {
  uuid: '',
  host: '',
  device: '',
  cookies: {},
  login: {},
  request: null,
};

Function to obtain the UUID:

import axios from 'axios';
function getLoginUUID(): Promise<string> {
  const modeQuery = '?mod=desktop';
  const redirectUrl = wxApi.webwxnewloginpage + modeQuery;
  return axios.get(wxApi.jslogin, {
    params: {
      redirect_uri: redirectUrl,
      appid: wxApp.appId,
      fun: 'new',
      lang: 'zh_CN',
      _: +new Date()
    }
  }).then(res => {
    const codeMatch = res.data.match(/window.QRLogin.code = (\d+);/) || [];
    const uuidMatch = res.data.match(/uuid = "(.*?)"/) || [];
    const code = codeMatch[1] || '';
    const uuid = uuidMatch[1] || '';
    return code === '200' ? uuid : '';
  }).catch(error => { console.error(error); return ''; });
}

Function to display the QR code in the terminal:

import QRCode from 'qrcode';
function showQrcode(uuid: string) {
  const url = wxApi.qrcodeLogin + `/${uuid}`;
  if (uuid) {
    QRCode.toString(url, { type: 'terminal' }).then(image => {
      console.log(image);
      console.log(url);
    });
  }
}

Polling login status:

import axios from 'axios';
function checkLogin(uuid: string): Promise<Login> {
  const now = +new Date();
  return axios.get(wxApi.login, {
    params: {
      r: Math.floor(now / 1579),
      _: now,
      loginicon: true,
      uuid,
      tip: 0,
    }
  }).then(res => {
    const statusMatch = res.data.match(/window.code=(\d+);/) || [];
    const redirectMatch = res.data.match(/window.redirect_uri="(.*?)"/) || [];
    const status = statusMatch[1] || LoginStatus.Error;
    const redirect = redirectMatch[1] || '';
    return { status, redirect };
  }).catch(error => { console.error(error); return { status: LoginStatus.Error, redirect: '' }; });
}

Main login flow:

async function login(wxData) {
  const { mode } = wxData;
  const uuid = await getLoginUUID(mode);
  let result = { status: LoginStatus.Wait, redirect: '' };
  showQrcode(uuid);
  while (result.status !== LoginStatus.Timeout) {
    result = await checkLogin(uuid);
    switch (result.status) {
      case LoginStatus.Scanned:
        console.log('Scanned');
        break;
      case LoginStatus.Success:
        return { ...result, uuid, isLogin: true };
      case LoginStatus.Error:
        return { ...result, uuid, isLogin: false };
      default:
        console.log(result);
    }
  }
  return { ...result, uuid, isLogin: false };
}

Fetching login information (XML parsing and cookie handling):

import axios from 'axios';
import { XMLParser } from 'fast-xml-parser';
import setCookie from 'set-cookie-parser';
function getLoginInfo(redirect, wxData) {
  const headers = { 'client-version': wxApp.uosPatchClientVersion, 'extspam': wxApp.uosPatchExtspam };
  return axios.get(redirect, { headers, params: { fun: 'new', version: 'v2' } })
    .then(res => {
      const xmlParser = new XMLParser();
      const { error: loginInfo = {} } = xmlParser.parse(res.data);
      const cookies = setCookie.parse(res.headers['set-cookie']);
      return { loginInfo, cookies };
    })
    .catch(error => { console.error(error); return { loginInfo: {}, cookies: {} }; });
}

Creating a request instance that automatically adds BaseRequest and cookies:

import axios from 'axios';
export function createWxRequest(wxData) {
  const { host, login, device, cookies } = wxData;
  const { skey, wxsid, wxuin } = login;
  const baseParams = { Uin: wxuin, Sid: wxsid, Skey: skey, DeviceID: device };
  const defaultOptions = { baseURL: host, timeout: 5000, headers: { 'User-Agent': wxApp.useAgent } };
  const instance = axios.create(defaultOptions);
  const rawPost = instance.post;
  instance.post = function(url, data = {}, config = {}) {
    const { needBase, ...otherData } = data;
    let newData = otherData;
    if (needBase) newData = { ...otherData, BaseRequest: baseParams };
    const cookie = cookies.map(({ name, value }) => `${name}=${value}`).join(';');
    const { headers } = config;
    config.headers = { ...headers, 'Cookie': cookie };
    return rawPost.call(this, url, newData, config);
  };
  return instance;
}

Fetching WeChat initialization data:

async function getWxInitInfo(wxData) {
  const { request } = wxData;
  request.post(wxApi.webwxinit, { '_': +new Date(), needBase: true }, { headers: { 'content-type': wxApp.jsonContentType } })
    .then(res => { console.log(res.data); });
}

Putting everything together:

import { URL } from 'url';
const wxData = { ...wxOption };
const { isLogin, uuid, redirect } = await login(wxData);
if (!isLogin) return;
const { origin } = new URL(redirect);
wxData.uuid = uuid;
wxData.host = origin;
const { loginInfo, cookies } = await getLoginInfo(redirect, wxData);
const deviceId = `e${Array.from({ length: 15 }, () => Math.floor(Math.random() * 9)).join('')}`;
wxData.login = loginInfo;
wxData.cookies = cookies;
wxData.device = deviceId;
const wxRequest = createWxRequest(wxData);
wxData.request = wxRequest;
await getWxInitInfo(wxData);

At this point the bridge API login functionality is complete; further features will be covered in future articles.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

TypeScriptNode.jsaxiosloginWeChatBridge API
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.