WorkflowsMCP

WeChat official account reply-window alerts → Feishu

Tracks the WeChat 48-hour reply window per conversation and alerts Feishu before it closes.

@workflowsmcpVerified supporttriagecustomer-feedbackreply-windowautomation

When someone messages your WeChat official account, you have 48 hours and 5 replies before the window closes — after that WeChat blocks further messages until they write again. This workflow tracks time left per conversation and alerts Feishu before the deadline. Built on the public-account customer service API — not WeChat Kefu, a separate product with the same 48-hour rule on its own system. Needs an HTTP endpoint for WeChat's callbacks and an authenticated official account — confirm 客服消息 is on your backend's permission list first.

How it flows

  1. 01

    Customer messages the official account

    Only the 'user sent a message' scenario opens the 48-hour/5-reply window this workflow tracks — menu clicks, new follows and QR scans each get a separate 1-minute allowance that doesn't feed it.

  2. 02

    Callback logs or refreshes the clock

    A new openid opens a window; a returning one resets last_user_msg_at and restarts the 48 hours.

  3. 03

    Scheduled scan finds windows nearing the deadline

    Every 5–10 minutes, a job checks for conversations past your alert threshold but still unanswered and inside the 48 hours — the piece WeChat has no native equivalent for.

  4. 04

    Signed alert posted to Feishu

    The webhook call carries the openid, elapsed time, and minutes left before the window closes.

  5. 05

    Support replies from the OA backend

    A person sends the reply, or explicitly confirms before an automated one fires — never an unattended auto-reply into a live conversation.

  6. 06

    Replied conversation drops off the alert queue

    Once WeChat accepts the reply, the scan stops counting that conversation down.

  7. 07

    Unanswered window closes at 48 hours

    Mark it closed and stop alerting — further sends return error 45015. Optionally post one final "window missed" notice to Feishu, distinct from the earlier warnings.

Set up each app

Work through these in order — later apps usually need a token or an id from an earlier one.

WeChat MP

Starts the 48-hour clock and sends the reply

  1. 01

    Confirm the customer service API is actually on, then turn on the callback

    This workflow calls the customer service message API (`message/custom/send`). That endpoint's own applicable-scope table lists both 公众号 (formerly 订阅号) and 服务号 as "仅认证" — official wording: "仅允许企业主体已认证账号调用,未认证或不支持认证的账号无法调用". The gate is authentication of the account as a business entity, not the 公众号/服务号 subtype: an authenticated 公众号 qualifies exactly like an authenticated 服务号 does, and most personal accounts can't authenticate at all regardless of subtype. Don't take that as a green light for an unauthenticated account: open your Official Accounts Platform (mp.weixin.qq.com) → Development (开发) → Basic Configuration (基本配置), and look for 客服消息 in your own interface's permission list before building anything else. If it's not there, your account isn't authenticated yet — that's the actual gate. While you're on that page, note your AppID and AppSecret (further down the same Basic Configuration screen — the send step later needs both), set a Server URL, a Token (a string you invent — not an access_token, and not the AppSecret) and leave encryption on plaintext mode; safe/encrypted mode adds an AES-decryption step this workflow doesn't cover.

  2. 02

    Verify the callback URL

    WeChat GETs your Server URL once, with signature, timestamp, nonce and echostr as query params. Sort [token, timestamp, nonce] lexically, join them, SHA1 the result, and compare to signature. On a match, return echostr as plain text — no quotes, no JSON wrapper. Get this wrong and WeChat never turns the callback on, silently.

    GET callback — signature check
    import hashlib
    
    signature, timestamp, nonce, echostr = data.signature, data.timestamp, data.nonce, data.echostr
    token = "YOUR_TOKEN"  # the string you set in Basic Configuration, not an access_token
    
    parts = sorted([token, timestamp, nonce])
    hashcode = hashlib.sha1("".join(parts).encode()).hexdigest()
    return echostr if hashcode == signature else ""
  3. 03

    Log the message and start (or refresh) the clock

    WeChat's own customer-service tools manage agent seats and session assignment, but nothing in the API counts down to a window closing — that tracking is this workflow's own job. On every POST callback, WeChat waits 5 seconds and retries up to 3 times if you don't respond, so acknowledge with an empty 200 immediately and do the real work asynchronously. Only the 'user sent a message' event feeds the 48-hour/5-reply clock — menu clicks, new follows and QR scans each carry their own 1-minute allowance and must not reset or extend it. Upsert (openid, last_user_msg_at) keyed on FromUserName from the XML body: a brand-new row opens the window, and a message from an openid already in the table refreshes last_user_msg_at and restarts the 48 hours — skip that refresh and the alert queue drifts out of sync with what WeChat actually enforces.

  4. 04

    Get a stable access token

    Use `stable_token`, not the older `token` endpoint — the old one invalidates the previous token on every refresh, so two processes calling it independently kick each other out (error 40001). `stable_token` is isolated from that endpoint and, with `force_refresh: false`, hands back the still-valid cached token to concurrent callers instead. It's still WeChat's own recommendation to fetch it from one place your other services call, rather than have every instance refresh independently. Token is valid 7200 seconds.

    POST https://api.weixin.qq.com/cgi-bin/stable_token
    {
      "grant_type": "client_credential",
      "appid": "YOUR_APPID",
      "secret": "YOUR_APPSECRET",
      "force_refresh": false
    }
  5. 05

    Reply to the customer — a person sends it, not the alert

    The Feishu alert tells a support agent a window is closing; it does not reply on its own. Have the agent reply from the OA backend directly, or, if you wire this call up, require an explicit confirm click before it fires — a wrong automated reply lands inside a live customer conversation and can't be recalled. `touser` is the `FromUserName` your callback logged; `access_token` is the `stable_token` response's `access_token` field. Once WeChat sees the reply, remove that conversation from the alert queue. A send attempt after the 48 hours have lapsed comes back as error 45015 (response out of time limit or subscription canceled — for this workflow's use case it almost always means the former) — treat that as confirmation the window is closed, not a bug.

    POST https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
    {
      "touser": "OPENID",
      "msgtype": "text",
      "text": { "content": "Thanks for waiting — following up now." }
    }

Feishu

Delivers the alert before the window closes

  1. 01

    Add a custom bot to the support group

    Inside the Feishu group that should receive alerts: group settings → Bots (群机器人) → Add Bot → Custom Bot (自定义机器人). Name it, and copy the webhook URL it generates — it looks like `open.feishu.cn/open-apis/bot/v2/hook/{hook_id}`, one per group.

  2. 02

    Turn on signature verification

    On the same bot's Security Settings (安全设置), enable "Signature verification" (签名校验) and copy the secret it shows you. Without this, anyone who gets hold of the webhook URL can post into your support channel.

  3. 03

    Sign and send the alert

    timestamp must be seconds, not milliseconds — the most common mistake porting this from DingTalk or WeCom bot signatures, which use milliseconds. Feishu rejects a timestamp more than 3600 seconds from its own clock. The signing string is `{timestamp}\n{secret}`, used as the HMAC-SHA256 key over an empty message; base64-encode the digest as `sign`. Rate limit is 100 requests/minute and 5/second per bot — well above what a reply-window monitor needs, but avoid batching every alert onto the exact minute or half-hour mark, where Feishu's own docs note other bots' traffic tends to spike.

    Sign the webhook payload
    import time, hmac, hashlib, base64
    
    timestamp = str(int(time.time()))          # seconds, not milliseconds
    secret = "YOUR_BOT_SECRET"                 # from the bot's Security Settings
    string_to_sign = f"{timestamp}\n{secret}"
    hmac_code = hmac.new(string_to_sign.encode(), b"", digestmod=hashlib.sha256).digest()
    sign = base64.b64encode(hmac_code).decode()
    
    payload = {
        "timestamp": timestamp,
        "sign": sign,
        "msg_type": "text",
        "content": {"text": "Conversation openid=oXXXX has 30 minutes left on its 48-hour window."},
    }
    # POST to the webhook URL you copied in the "Add a custom bot" step
  4. 04

    Add the remaining time and a backend link

    Swap `msg_type: "text"` for `"interactive"` once the basic alert works, and include the minutes remaining and a link back to the OA backend conversation. That turns "something is closing" into "here is exactly what to do about it" for whoever is on support that hour.