MINARA

WeCom (企业微信)

WeCom self-built app messaging with SHA1 sort + AES-256-CBC callback envelope. The standard channel for enterprise China deployments.

🟡 Outbound-ready, inbound supported, built around the self-built-application path (most common deployment shape). Routes through the same legacy SHA1 + AES envelope WeChat OA uses, so the shared wxcrypt helper covers both providers.

What you get

  • Outbound text messaging via POST /cgi-bin/message/send to one or more touser recipients (or @all for the whole agent).
  • Inbound callback events at /webhooks/wecom. WeCom signs every callback with msg_signature (SHA1 over [token, timestamp, nonce, encrypt] sorted lexicographically). Body is AES-256-CBC-encrypted under the 43-char EncodingAESKey.
  • Per-app access_token cached for 2 hours with auto-refresh.
  • Text only this PR. Image / file upload (via "upload media" endpoint) is a future enhancement.
  • 2048-char text limit (4096 for markdown, not yet wired).

Setup

1. Create a self-built application

  1. Sign in to WeCom admin console
  2. Go to "Applications & Mini Programs" → "Self-built" → "Create"
  3. Fill in the application metadata; on the detail page note the AgentID and Secret
  4. The corp-level CorpID lives under "My Corp" → "Corp Info"

2. Enable callback (optional, for inbound)

  1. Open the application detail page → "API Receive" → "Set API Receive"
  2. Set the URL to https://<your-host>/webhooks/wecom
  3. Fill in a Token (any opaque string) and click "Random" for the EncodingAESKey (must be 43 base64 chars)
  4. Click "Save". WeCom hits the URL with a GET handshake; Minara decrypts the echostr and echoes it back, finalising the binding.

3. Configure Minara

minara auth messaging add
# pick `wecom`; paste corp id, agent id, secret, default touser,
# callback token, EncodingAESKey.

Or set env vars directly:

WECOM_CORP_ID=ww<...>
WECOM_AGENT_ID=1000002
WECOM_SECRET=<application secret>
WECOM_DEFAULT_TOUSER=user1|user2     # or @all
WECOM_CALLBACK_TOKEN=<callback token>
WECOM_CALLBACK_AES_KEY=<43-char EncodingAESKey>

WECOM_DEFAULT_TOUSER accepts the WeCom convention: pipe-separated user ids, or @all for the whole agent's audience.

4. Test

minara auth messaging test wecom

Each touser in the default list receives "✅ Minara gateway test ping".

Inbound webhook

WeCom carries the message signature in the URL query, not in headers. Minara validates msg_signature against SHA1(sort([token, timestamp, nonce, encrypt]).join("")) (Tencent's legacy IM scheme) before decrypting the body.

GET URL-verification handshake: WeCom sends ?msg_signature=…&timestamp=…&nonce=…&echostr=<base64>. Minara checks the signature, decrypts echostr with the AES key (IV = first 16 bytes of the key), and echoes the plaintext. A wrong token or AES key returns 401 / 500.

Inner XML payload after decryption:

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

Only MsgType=text messages are emitted to the agent today. Image, voice, and event types are silently dropped.

Limits & caveats

  • access_token and callback token are different. The first is WeCom's per-app OAuth, the second is your shared signing secret for callbacks. Keep them straight in your env file.
  • @all is a frequent pitfall. It blasts a notification to every user in the agent's scope; pin WECOM_DEFAULT_TOUSER to specific user ids for production.
  • AES key length is mandatory. WeCom rejects keys that do not decode to exactly 32 bytes (43 base64 chars before padding).

Troubleshooting

"Get access token failed (errcode 40013)"

  • WECOM_CORP_ID does not match the corp the secret belongs to.

"Inbound webhook returns 401"

  • The msg_signature does not match. Most common cause: a typo in WECOM_CALLBACK_TOKEN. Less common: your reverse proxy strips the signature query.

"signCallbackParams length mismatch"

  • WECOM_CALLBACK_AES_KEY is not 43 characters. Re-generate from the WeCom console.

Reference

On this page