DingTalk Messaging Skill
Responsible for all operations related to DingTalk message sending. This document is a
Strategy Guide that only includes decision logic and workflows. For complete API request formats, refer to the lookup index in
at the end of this document.
Overview of Four Messaging Channels
| Channel | Use Case | Authentication Method | Features |
|---|
| Webhook Bot | Send notifications to designated groups | No token required, URL includes credentials | Simplest to use; supports signature-based security mode |
| Internal Enterprise App Bot | One-on-one private messages / group chat messages | New version accessToken | Supports message recall and read status query; requires userId or openConversationId |
| Work Notification | Push to "Work Notification" session as an app identity | Legacy access_token + agentId | Can push to all users/departments; appears in Work Notifications instead of chats |
| sessionWebhook | Directly reply to the current conversation in callbacks | No authentication required | Callback messages include a temporary URL, valid for approximately 1.5 hours |
Workflow (Before Each Execution)
- Understand User Intent First → Determine which messaging channel it belongs to (see "Scenario Routing" below)
- Read Configuration → Use a single command to read all required key-values for the channel at once, instead of querying multiple times. (Retained across sessions, shared by all dingtalk-skills) For specific required configurations, see the "Configurations Required for Each Channel" table below
- Collect Only Missing Configurations for the Channel → Ask all missing configurations at once, not one by one
- Persistence → Write to config, no need to ask again in subsequent operations
- Execute Task
Configurations Required for Each Channel
| Channel | Required Configurations | Source Description |
|---|
| Webhook | | Group Settings → Smart Group Assistant → Add Custom Bot → Copy URL |
| Webhook (with Signature) | Additional | Obtained when selecting "Signature" mode during bot creation |
| Bot Messaging | + | Open Platform → App Management → Credential Information |
| Work Notification | + + | agentId is located in App Management → Basic Information |
- = (exactly the same, no additional configuration required)
- Credentials must not be printed in full in outputs; only show the first 4 characters + during confirmation
Execution Specifications (Recommended)
Always Execute via Script Files: For commands involving variable substitution (
), pipes (
) or multi-line logic, always use
to write to
then execute with
. Do not inline them in the terminal. Inlined commands may be truncated or contaminated by terminal tools, leading to variable reading failures.
Prohibit heredoc (
), as it will be truncated by tools.
Typical Script Template (Read Config → Get Token (with Cache) → Execute API):
bash
#!/bin/bash
set -e
CONFIG=~/.dingtalk-skills/config
# Read all required configurations at once
APP_KEY=$(grep '^DINGTALK_APP_KEY=' "$CONFIG" | cut -d= -f2-)
APP_SECRET=$(grep '^DINGTALK_APP_SECRET=' "$CONFIG" | cut -d= -f2-)
# Token Cache: Reuse within validity period to avoid repeated requests
CACHED_TOKEN=$(grep '^DINGTALK_ACCESS_TOKEN=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
TOKEN_EXPIRY=$(grep '^DINGTALK_TOKEN_EXPIRY=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
NOW=$(date +%s)
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then
TOKEN=$CACHED_TOKEN
else
RESP=$(curl -s -X POST https://api.dingtalk.com/v1.0/oauth2/accessToken \
-H 'Content-Type: application/json' \
-d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}")
TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
EXPIRY=$((NOW + 7000)) # Token validity is 2 hours, set to expire about 13 minutes early
# Update cache (delete old values first then append)
sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG"
echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG"
echo "DINGTALK_TOKEN_EXPIRY=$EXPIRY" >> "$CONFIG"
fi
# Append specific API calls here
JSON Field Extraction:
grep -o '"key":"[^"]*"' | cut -d'"' -f4
Handling Missing Message Content
If the user does not specify message content,
do not fabricate it yourself; first ask the user what content they want to send. Only when the user clearly states "send a random test message" should you use the default content:
This is a test message from a DingTalk bot.
Scenario Routing (Decision Logic After Receiving User Request)
User wants to send a message
├─ Send to a group?
│ ├─ General group message (including @someone) → Ask the user to select a sending method (see "Group Message Sending Method Selection" below)
│ ├─ Explicitly needs recall or read status query → Enterprise Bot Group Chat (skip the inquiry directly)
│ └─ Processing bot callback, direct reply → sessionWebhook
├─ Send to an individual?
│ ├─ Send private message as a bot → Enterprise Bot One-on-One Chat
│ └─ Push work notification as an app identity → Work Notification
├─ Recall message / check read status?
│ ├─ Bot message → Enterprise Bot's recall/read status API
│ └─ Work notification → Work notification query/recall API
└─ Reply to messages received by the bot? → sessionWebhook
Group Message Sending Method Selection
When a user initiates a group message request, must first ask them to select a method and explain the required configurations for each:
Which method would you like to use to send this group message?
| Method | Required Information | How to Obtain | Description |
|---|
| Webhook Bot | | Group Settings → Smart Group Assistant → Add Custom Bot → Copy URL | No app permissions required, simplest configuration; supports @someone () |
| Internal Enterprise App Bot | (group session ID) | When the bot receives a group message, the field in the callback body is this value | Requires /; supports message recall and read status query |
Webhook is recommended, as it only requires a URL and no app permissions.
After receiving the user's selection, collect configurations as follows:
- If Webhook is selected: Collect (if signature is enabled, also collect ), then execute after persistence
- If Enterprise Bot is selected: Collect , reuse existing / (collect them together if not configured), then call
Obtain the corresponding token by channel before executing the API:
| Channel | Token Type | Obtaining Method | Usage Method |
|---|
| Bot Messaging | New version accessToken | POST https://api.dingtalk.com/v1.0/oauth2/accessToken
| Request header x-acs-dingtalk-access-token
|
| Work Notification | Legacy access_token | GET https://oapi.dingtalk.com/gettoken?appkey=&appsecret=
| URL parameter |
| Webhook | No token required | — | Direct POST to Webhook URL |
| sessionWebhook | No token required | — | Direct POST to the sessionWebhook URL in the callback |
All tokens are valid for 2 hours. Re-obtain them when encountering 401 errors. For specific request/response formats, refer to the corresponding sections in api.md.
Identity Identification (Key Decision Knowledge)
All message sending APIs only accept userId (staffId), not unionId. This has been verified through actual API calls.
| Identifier | Scope | Usable for Sending Messages |
|---|
| (= ) | Unique within a single enterprise | ✅ The only accepted ID |
| Unique across organizations | ❌ Will be judged as an invalid user |
How to Obtain userId
- Bot Callback (Most commonly used): field in the message body
- unionId → userId:
POST /topapi/user/getbyunionid
- Mobile Number → userId:
POST /topapi/v2/user/getbymobile
- Admin Backend: PC DingTalk → Workbench → Admin Backend → Address Book
Identity Fields in Callback Messages
| Field | Meaning | Reliability |
|---|
| Sender's userId | Always exists in internal enterprise groups; may be empty for external users in external groups |
| Sender's unionId | Always exists |
For details on userId ↔ unionId conversion APIs:
grep -A 8 "^#### userId ↔ unionId" references/api.md
Note that
(no underscore) has a value, while
(with underscore) may be empty in some enterprises.
Quick Reference for Message Types
Webhook Message Types
Specify directly in the
field of the request body:
|
|
|
|
Complete JSON formats for each type:
grep -A 30 "^#### Text Message" references/api.md
(replace "Text Message" with "Markdown Message", "ActionCard", etc.)
Bot Message Types
Specify via
+
(JSON string):
| msgKey | Type | Key Fields in msgParam |
|---|
| Text | |
| Markdown | , |
| ActionCard | , , , |
| Link | , , , |
| Image | |
Important:
must be a
JSON string, not an object. For complete formats:
grep -A 16 "^### Message Types" references/api.md
Work Notification Message Types
Specify in the
field of the
object:
|
|
Note that
uses an underscore for work notifications (different from
for Webhook). For complete formats:
grep -A 62 "^### Work Notification Message Types" references/api.md
Typical Scenarios
"Send a group message to notify everyone" / "Group message @someone"
→ First ask the user to select between Webhook or Enterprise Bot per "Group Message Sending Method Selection", then execute after collecting corresponding configurations.
Webhook supports @someone: Add
(array of user IDs) in the body, and write
in the body content to highlight the user.
"Send a deployment notification to the R&D group using Markdown"
→ First ask the sending method (same as above). If Webhook is selected, construct the body with
; if Enterprise Bot is selected, use
.
"Send a message to Zhang San"
→
Enterprise Bot One-on-One Chat. Requires
/
+ Zhang San's userId. Call
.
"Send a message with buttons to the R&D group"
→ First ask the user to select between Webhook or Enterprise Bot per "Group Message Sending Method Selection", then execute after collecting corresponding configurations.
Webhook uses
; Enterprise Bot uses
.
"Send a work notification to all staff"
→
Work Notification. Requires
/
/
. Set
.
"Recall the message I just sent"
→ Find the
(for bots) or
(for work notifications) returned from the last send, then call the corresponding recall API.
"Reply to messages received by the bot"
→
sessionWebhook. Retrieve the
URL from the callback, then directly POST to it without any authentication.
Quick Reference for Error Handling
| Scenario | Error Characteristics | Handling |
|---|
| Webhook | 310000 keywords not in content
| Must include custom keywords |
| Webhook | | Check signature calculation and timestamp |
| Webhook | | Limited to 20 messages/minute, wait and retry |
| Bot | is non-empty | userId is invalid, confirm the user is in the organization |
| Bot | flowControlledStaffIdList
is non-empty | Rate-limited, retry later |
| Work Notification | | Incorrect agentId |
| Work Notification | | access_token expired |
| General | 401 / 403 | Token expired / Insufficient permissions |
Complete error code table:
grep -A 33 "^## Error Codes" references/api.md
Required App Permissions
| Function | Permission |
|---|
| Bot One-on-One Chat | |
| Bot Group Chat | |
| Message Read Status Query | |
| Message Recall | |
| Work Notification | Message.CorpConversation.AsyncSend
|
| Webhook / sessionWebhook | No app permissions required |
references/api.md Lookup Index
After confirming the operation, use the following commands to extract complete API details (request formats, parameter tables, return value examples) from the corresponding sections in
:
bash
grep -A 196 "^## 一、群自定义 Webhook 机器人" references/api.md
grep -A 19 "^### 加签计算" references/api.md
grep -A 192 "^## 二、企业内部应用机器人" references/api.md
grep -A 36 "^#### 钉钉身份标识体系" references/api.md
grep -A 16 "^### 消息类型" references/api.md
grep -A 145 "^## 三、工作通知" references/api.md
grep -A 60 "^## 四、sessionWebhook" references/api.md
grep -A 33 "^## 错误码" references/api.md
grep -A 30 "^#### 文本消息" references/api.md
grep -A 30 "^### 批量发送单聊消息" references/api.md
grep -A 30 "^### 发送工作通知" references/api.md