How to Add an AI Assistant to DingTalk? 3 Approaches Compared (With Stream Mode Code)

There are three ways to wire AI into DingTalk — picking wrong doesn't just mean more code, it means messages that never arrive, rate limits, or a data boundary you can't explain to compliance

2026-09-21 · Leo · Tutorial · 10 min read
TL;DR — Three paths to an AI assistant in DingTalk: Approach A: internal enterprise app robot with Stream mode (no public callback URL needed, full send/receive control, best for production); Approach B: custom group robot webhook (works in 10 minutes, but push-only — it cannot receive messages, so it can't do conversations); Approach C: third-party SaaS (fastest to launch, but conversations flow through someone else's platform). This post walks through all three with code, plus 5 common pitfalls — Stream disconnects, connected-but-silent robots, webhook rate limits, card callback failures — and a pre-launch checklist.
⚠️ This is a generic tutorial; API fields follow the official DingTalk documentation. Code samples are examples — adjust to your company's security and compliance requirements before going live. No customer cases involved.

TL;DR Table: The Three Approaches

DimensionA: Internal Robot + StreamB: Custom Group Robot WebhookC: Third-Party SaaS
Receive messages✅ 1:1 and group, send and receive❌ Push to group only✅ Platform relays both ways
Public endpoint requiredNo — your service dials out over a long connectionNo (outbound only)No
Data boundaryMessages and replies stay on your serversPush content via DingTalk serversConversations via third-party platform
Time to launch1–3 days (mostly connection and card debugging)~10 minutesHalf a day to 1 day (config-driven)
Best forProduction AI assistant, FAQ, workflow queriesAlerts, daily digests, CI/CD noticesFast validation, no dev team

One line: conversations need A, notifications fit B, quick validation uses C. The classic mistakes: trying to build a chatbot on B (it can't receive group messages), or jumping to C without checking the data boundary.

Approach A: Internal Enterprise App Robot + Stream Mode (Recommended)

This is DingTalk's recommended path: your service uses Stream mode to dial out and hold a WebSocket long connection to DingTalk. An employee messages the robot → DingTalk pushes the message down that connection → your service calls the AI → the reply goes back up the same connection. Because there is no inbound HTTP callback, you need no public IP, no registered domain, and no inbound firewall port — it runs fine on an intranet-only machine. That is the big difference from the HTTP-callback route.

Step 1: Create the internal app and robot (~10 min)

DingTalk developer console → "App Development" → "Internal Enterprise App" → create. Then add a robot inside the app and set the message receiving mode to Stream mode. Note three values:

Before publishing, scope visibility to one small department as a canary, then widen it after validation.

Step 2: Receive messages over Stream mode (~30 min, incl. debugging)

The core of Stream mode: on startup, exchange your credentials for a temporary connection endpoint, connect, and subscribe to the robot message topic. If the connection drops, reconnect — that's the root of Pitfall 1 below.

# dingtalk_stream_bot.py — open a long connection and subscribe to robot messages
from dingtalk_stream import DingTalkStreamClient, Credential
from dingtalk_stream.chatbot import ChatbotHandler, ChatbotMessage

class AIHandler(ChatbotHandler):
    async def process(self, callback):
        msg = ChatbotMessage.from_dict(callback.data)
        # ack first, process asynchronously — never wait for the AI inside the callback (Pitfall 3)
        self.reply_text("Got it, working on it…", msg)
        question = msg.text.content.strip()
        answer = ask_ai(question)          # your own AI service / knowledge base
        self.reply_text(answer, msg)
        return 200, "OK"

credential = Credential(CLIENT_ID, CLIENT_SECRET)      # read from env vars, never hardcode
client = DingTalkStreamClient(credential)
client.register_callback_handler("/v1.0/im/bot/messages/get", AIHandler())
client.start_forever()                 # has built-in reconnect, but still needs process supervision

The most common debugging trap: the topic string is wrong — robot messages use a fixed topic, and a typo shows up as "connected but never receives anything", with no error in the log at all.

Step 3: Send messages and cards (proactive push + conversation reply)

Reply to a conversation through the robot message API; for clickable interactions like approval progress or ticket status, use interactive cards. Both go through the same access_token, which must be cached centrally (valid 7200 seconds, with a fetch rate limit).

# dingtalk_client.py — centralized token cache + send robot message
import time, requests

_token, _ts = "", 0

def get_token():
    global _token, _ts
    if _token and time.time() - _ts < 7000:          # 200s safety margin
        return _token
    r = requests.post("https://api.dingtalk.com/v1.0/oauth2/accessToken",
                      json={"appKey": CLIENT_ID, "appSecret": CLIENT_SECRET},
                      timeout=5).json()
    _token, _ts = r["accessToken"], time.time()
    return _token

def reply_text(user_id, text):
    requests.post(
        "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",
        headers={"x-acs-dingtalk-access-token": get_token()},
        json={"robotCode": ROBOT_CODE, "userIds": [user_id],
              "msgKey": "sampleText", "msgParam": '{"content": "%s"}' % text[:1800]},
        timeout=5)

Note the last line: DingTalk wraps text content inside a JSON string in msgParam, so truncate or split long answers — otherwise the call fails outright (Pitfall 5).

Step 4: Wire up your AI and knowledge base

Swap ask_ai() for your RAG knowledge base or LLM service. Three engineering points:

Approach B: Custom Group Robot Webhook (10 minutes, push-only)

Group settings → "Smart Group Assistant" → "Add Robot" → "Custom" gives you a webhook URL. POST JSON to it and the message lands in the group (optional signed verification):

curl 'https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"msgtype":"text","text":{"content":"Deploy done: order service v2.3 is live"}}'

Its role is one-way notification: monitoring alerts, CI/CD results, daily digests. Three hard limits: it cannot receive group messages (no conversations), each robot has a per-minute send cap (high-frequency alerts must be aggregated), and @-mentions need a phone number or userId (not a display name). Text, markdown, link, and actionCard types are supported. Good for "tell humans what the system did" — wrong for "humans ask, system answers".

Approach C: Third-Party SaaS (fastest — but check the data boundary first)

Managed platforms let you authorize DingTalk and configure a knowledge base from a web console — no long-connection code to write, live within half a day. Two situations fit: no dev capacity, or you want to validate the value of an AI assistant before committing engineering time.

But ask four questions before signing, or you'll pay for it later:

  1. Where is data stored: do employee questions and uploaded documents live on their platform? In which region?
  2. Can you export: if you switch later, can the knowledge base and conversation history leave with you?
  3. Is on-prem possible: after validation, can it migrate to the self-hosted Approach A form?
  4. How is it billed: per seat or per call? Who pays the underlying LLM cost?

Side by Side: DingTalk vs Feishu vs WeCom — Where the Effort Actually Goes

DimensionDingTalkFeishuWeCom
Main integration pathInternal robot + Stream long connectionEvent subscription (long connection or HTTP callback)Custom app + HTTP callback
Public endpoint neededNo — the client dials outEither, long connection avoids itYes: HTTPS callback required
Callback timing pressureNone on the long connectionHTTP callbacks still expect a fast returnMust respond within 5 seconds or it retries
Deduplication keyMessage IDEvent IDMsgId
Intranet friendlinessHigh: no inbound portMediumLow: must expose a callback URL

Conclusion: if your intranet restricts outbound traffic and you don't want to apply for a public domain just for callbacks, DingTalk's Stream mode is the easiest of the three to land. WeCom's 5-second callback limit needs the most engineering cushioning; Feishu sits in between. When you integrate several platforms, extract "receive → call AI → reply" into one shared gateway and keep platform differences in an adapter layer — otherwise adding the next platform means starting over.

Five Common Pitfalls and Fixes

Pitfall 1: The Stream connection dies overnight and nobody notices

Symptom: the robot is unresponsive in the morning; restarting the service fixes it.

Cause: an intermediate network device killed the long connection and the client never reconnected, or the process exited on an unhandled exception.

✅ Fix: enable client-side auto-reconnect and supervise the process (systemd/container restarts it on exit); add a liveness probe — send a heartbeat message to yourself on a timer and alert when nothing arrives for N minutes.
Pitfall 2: Connected successfully, but messages never arrive

Symptom: the log says connected, employees get no response, and there is no error either.

Cause: the subscribed topic is wrong; or the robot isn't published and visibility doesn't include the asker; or the registered callback path doesn't match the message type.

✅ Fix: verify the topic constant (robot messages use a fixed topic); confirm the app is published with the test account in scope; log the whole raw callback payload so you can first confirm "did it arrive" before asking "why no reply".
Pitfall 3: Slow AI causes duplicate or empty replies

Symptom: for complex questions the same message gets answered two or three times, or the user only ever gets "Got it".

Cause: waiting synchronously for the AI inside the callback so the platform re-pushes; or sending an ack and then never following up.

✅ Fix: the callback should only dedupe and enqueue, then return immediately; run the AI in an async task and push the answer when it's ready; dedupe by message ID (Redis SETNX with TTL) with shared state across instances.
Pitfall 4: The group robot gets rate-limited and the alert never lands

Symptom: during an incident the alerts flood, and the most important ones never reach the group.

Cause: custom group robots have a per-minute send cap; over the cap, messages are dropped or rejected.

✅ Fix: aggregate alerts first (merge duplicates, roll up on a 1-minute window); retry with backoff on failure; send critical alerts through the internal robot's 1:1 channel instead of competing for the same group output.
Pitfall 5: Long replies fail or get truncated

Symptom: longer AI answers never arrive or only the first half shows up, with a parameter error from the API.

Cause: the text is wrapped in a JSON string inside msgParam — over-length content or unescaped quotes both break it.

✅ Fix: build msgParam with json.dumps instead of string concatenation; split long content into multiple messages by paragraph, or reply with a summary plus a link to the knowledge base page.

Pre-Launch Checklist (8 items)

  1. Does the long connection auto-reconnect, with process supervision that restarts it on exit?
  2. Is there a liveness probe — a periodic heartbeat that alerts past a threshold?
  3. Is message dedup in place (Redis/in-memory), shared across instances?
  4. Is access_token centrally cached, refreshed under lock, with expiry margin?
  5. Does the AI call have a timeout fallback (ack first, then a notice — never leave users hanging)?
  6. Are long replies split or summarized, and is msgParam serialized rather than hand-built?
  7. Is there sensitive-word/permission filtering before sending, and is robot visibility scoped by department?
  8. Are logs sanitized and Client Secrets kept in environment variables instead of the codebase?

Final Thoughts

No approach is universally better — the choice comes down to three things: how strict your data boundary is, how much dev capacity you have, and what scenario you're solving. Don't spend engineering on Approach A for a notification feed; don't build a chatbot on Approach B (it can't receive); don't sign up for Approach C without asking the four data-boundary questions. Many teams' real path: validate quickly with C or B, then migrate to A as the long-term self-hosted home.

If you only keep one line: conversations → A, notifications → B, validation → C — don't mix them.

💡 Further reading: For other IM platforms, see How to Build a WeCom AI Bot? 3 Approaches Compared and Feishu AI Bot: 30-Minute Quickstart; for a unified multi-IM strategy, see AI Agent in Enterprise IM: Wire Up Feishu/DingTalk/WeCom in 4 Steps.