Push Alerts to DingTalk via RPC
1. Overview
ThinkLink (TKL) RPC can call external HTTP endpoints (see RPC Model §1.3.2 Type 4) and forward device alerts to a DingTalk custom group bot in real time. This note shows how to push messages securely using DingTalk's HMAC signing mode.
2. Prerequisites
2.1 Create a DingTalk Custom Bot
- In a DingTalk group, open Group Settings → Group Assistant → Add Bot → Custom
- Under security settings, strongly recommend choosing "Additional Signing" (加签) mode (do not rely solely on keyword or IP whitelist)
- Copy the generated Webhook URL and Signing Secret (starts with
SEC)
Webhook: https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxxxx
Secret: SECxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2.2 Protect access_token and Secret (Required Reading)
⚠️ Security notice:
access_tokenis the bot's send credential,Secretis the signing key. Leakage of either allows an attacker to forge messages from the bot.
Follow these rules:
- Do not hard-code
access_tokenorSecretinto the RPC script, config files, or screenshots - Recommended: Store both in the device's (or asset's)
server_attrsand reference them viadevice.server_attrs.dingtalk_tokenand.dingtalk_secret - Always enable signing mode: DingTalk's "keyword" and "IP whitelist" modes are easy to bypass; signing is the only mode that resists forgery
- Rotate the credentials at least once a quarter (recreate the bot)
- Limit group membership strictly; remove bots that are no longer needed
- If you suspect a leak, delete the bot immediately and create a new one
3. Calling DingTalk from an RPC
3.1 Store Credentials in server_attrs
In Operations → Device Management → Device Detail → Attributes, add to server_attrs:
json
{
"dingtalk_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"dingtalk_secret": "SECxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}3.2 RPC Script: Signed Markdown Message
javascript
function rpc_script({ device, params, alarms, logger }) {
let token = device.server_attrs?.dingtalk_token;
let secret = device.server_attrs?.dingtalk_secret;
if (!token || !secret) {
logger.error("dingtalk credentials not configured", { eui: device.eui });
return null;
}
// DingTalk signature: HmacSHA256(timestamp + "\n" + secret), then base64 + URL encode
let timestamp = Date.now();
let stringToSign = `${timestamp}\n${secret}`;
let crypto = require("crypto");
let sign = encodeURIComponent(
crypto.createHmac("sha256", secret).update(stringToSign).digest("base64")
);
let url = `https://oapi.dingtalk.com/robot/send?access_token=${token}` +
`×tamp=${timestamp}&sign=${sign}`;
let title = `Device Alert - ${device.name}`;
let text = [
`### ${title}`,
``,
`- **EUI**: \`${device.eui}\``,
`- **Level**: **${(params.level || "").toUpperCase()}**`,
`- **Description**: ${params.desc || "n/a"}`,
`- **Time**: ${new Date().toISOString()}`
].join("\n");
return [
{
sleepTimeMs: 0,
type: "axios",
dnMsg: {
method: "POST",
url: url,
headers: { "Content-Type": "application/json" },
data: {
msgtype: "markdown",
markdown: { title: title, text: text },
at: {
atMobiles: params.at_mobiles || [],
isAtAll: !!params.at_all
}
},
timeout: 5000
}
}
];
}💡 DingTalk requires
timestampto be within 1 hour of server time. The signature must be recomputed for the current timestamp — never reuse a precomputed one.
3.3 RPC Script: Plain Text Message
javascript
function rpc_script({ device, params, alarms, logger }) {
let token = device.server_attrs?.dingtalk_token;
let secret = device.server_attrs?.dingtalk_secret;
if (!token || !secret) return null;
let timestamp = Date.now();
let crypto = require("crypto");
let sign = encodeURIComponent(
crypto.createHmac("sha256", secret)
.update(`${timestamp}\n${secret}`).digest("base64")
);
return [
{
sleepTimeMs: 0,
type: "axios",
dnMsg: {
method: "POST",
url: `https://oapi.dingtalk.com/robot/send?access_token=${token}×tamp=${timestamp}&sign=${sign}`,
headers: { "Content-Type": "application/json" },
data: {
msgtype: "text",
text: { content: params.content || `${device.name} alert` }
},
timeout: 5000
}
}
];
}4. RPC Parameter Configuration
| Attr Name | Type | Alias | Description |
|---|---|---|---|
content | string | Message content | Used in plain-text mode |
level | string | Alert level | low / mid / high / urgent |
desc | string | Alert description | Markdown-mode body text |
at_mobiles | object | @ mobile list | Array, e.g. ["13800000000"] |
at_all | boolean | @ everyone | Use sparingly |
5. Wiring into the Alert Flow
See WeCom AN §5 for the trigger script example — just change method to the name of this RPC.
6. Troubleshooting
| Symptom | Likely Cause |
|---|---|
errcode: 310000 sign not match | timestamp or secret mismatch; wrong signature encoding (must be base64 then URL encode) |
errcode: 130101 | Keyword mode active but message lacks the configured keyword |
| No message at all | Wrong access_token; bot disabled by group admin; TKL server cannot reach oapi.dingtalk.com |
| Rate limit | A single bot allows max 20 messages per minute |
7. Notes
- Signing requires a new signature per message — never reuse an old one
- Do not include customer PII, internal IPs, tokens, or other sensitive data in messages
- @-mentioned mobile numbers must be the registered numbers of group members
- For critical alerts, configure multiple notification channels (e.g. DingTalk + email) so a single-channel outage does not silence alerts