MINARA

WeChat OA (公众号)

WeChat Official Account customer-service messaging within the 48-hour reply window. Same crypto envelope as WeCom.

🟡 Outbound-ready, inbound supported, 48-hour window. Outbound customer-service messages are constrained: a user must have interacted with the OA in the last 48 hours, otherwise the platform returns errcode 45015 and the message is dropped. Production deployments that need cold push should use Template Messages (pre-approved templates), which are a future enhancement.

What you get

  • Outbound text via POST /cgi-bin/message/custom/send?access_token=... to any openid that has messaged your OA within the last 48 hours.
  • Inbound message events at /webhooks/wechat-oa. Same SHA1 sort
    • AES-256-CBC envelope as WeCom (the shared wxcrypt helper covers both). One small difference on the GET handshake: WeChat OA signs the 3-tuple [token, timestamp, nonce] (not 4-tuple).
  • Per-app access_token cached for 2 hours.
  • Text only this PR. Image / news / template messages are deferred.
  • 2048-char text limit.

Setup

1. Create / claim an Official Account

  1. Sign in to WeChat Public Platform (Service Account, not Subscription, is required for the customer-service API)
  2. "Settings" → "Basic Info": note the AppID and AppSecret
  3. "Settings" → "Server Configuration":
    • URL: https://<your-host>/webhooks/wecom
    • Token: any opaque string (becomes WECHAT_OA_TOKEN)
    • EncodingAESKey: click "Generate Random" (43 chars, becomes WECHAT_OA_AES_KEY)
    • Encryption mode: "Safe Mode" (encrypted) recommended
  4. Click "Submit". WeChat hits the URL with a GET handshake; Minara echoes back the (decrypted) plaintext.

2. Find a default openid

The customer-service API needs an openid (an opaque user identifier). Trigger any interaction from the test user (follow the OA, send a message, click a menu item). The inbound webhook will surface the openid in FromUserName. Save one as WECHAT_OA_DEFAULT_OPENID.

3. Configure Minara

minara auth messaging add
# pick `wechat_oa` from the list.

Or set env vars directly:

WECHAT_OA_APP_ID=wx<...>
WECHAT_OA_APP_SECRET=<opaque secret>
WECHAT_OA_TOKEN=<server config Token>
WECHAT_OA_AES_KEY=<43-char EncodingAESKey>
WECHAT_OA_DEFAULT_OPENID=<o...........>

4. Test

minara auth messaging test wechat_oa

Note: the test will fail with errcode 45015 if the default openid has not interacted with the OA in the last 48 hours.

Inbound webhook

WeChat OA's server-config GET handshake is slightly different from WeCom's. The 4-tuple [token, ts, nonce, encrypt] is replaced by a 3-tuple [token, timestamp, nonce]:

expected = sha1(sort([token, timestamp, nonce]).join(""))

POST signature is the same as WeCom: msg_signature in the URL query, 4-tuple sort, AES envelope inside the body.

Inner XML payload after decryption follows Tencent's classic shape:

<xml>
  <ToUserName>...</ToUserName>
  <FromUserName>...</FromUserName>
  <CreateTime>...</CreateTime>
  <MsgType>text</MsgType>
  <Content>...</Content>
  <MsgId>...</MsgId>
</xml>

Only MsgType=text events are forwarded to the agent.

Limits & caveats

  • 48-hour customer-service window. Outside the window, every outbound returns errcode 45015. Plan UX around passive replies (within 48 h of a user-initiated interaction) and pre-approved Template Messages for long-tail push.
  • Service Account vs Subscription Account. The customer-service API is only available to Service Accounts. Subscription Accounts cannot use this provider.
  • EncodingAESKey must be exactly 43 chars. Anything else fails decoding during boot.

Troubleshooting

"errcode 45015"

  • The openid did not interact with the OA in the last 48 hours. This is by design; switch to Template Messages for cold push (not yet wired in Minara).

"errcode 40001 / Token invalid"

  • access_token failed to refresh. Verify WECHAT_OA_APP_ID and WECHAT_OA_APP_SECRET against the OA console.

"Inbound webhook returns 401 on handshake"

  • The 3-tuple SHA1 check failed. Most common cause: typo in WECHAT_OA_TOKEN. Confirm the value matches "Token" in the OA's Server Configuration page.

Reference

On this page