Operations 11 min read

How to Build a DingTalk ChatOps Bot with Go: A Step‑by‑Step Guide

Learn why ChatOps matters and how to create a DingTalk chatbot using Go, covering setup, security signatures, HTTP headers, and integration with CI/CD, monitoring, and Kubernetes, complete with code examples and deployment steps for streamlined operations.

Ops Development Stories
Ops Development Stories
Ops Development Stories
How to Build a DingTalk ChatOps Bot with Go: A Step‑by‑Step Guide

What is the purpose?

Why develop ChatOps and what is it? Various Ops concepts such as DevOps, AIOps, ChatOps, NoOps all aim to intensify operational workflows, often making operations feel overwhelming. ChatOps uses chat interfaces to perform operational tasks, offering a lightweight way to automate work.

Typical application scenarios include:

Integrating with CI/CD tools like Jenkins, GitLab, GitHub to trigger builds, releases, and code merges.

Connecting with task management systems such as JIRA, Trello, Tower, ZenTao, email to handle tasks.

Working with Kubernetes platforms to create and deploy containers.

Receiving alerts from monitoring systems like Zabbix, Prometheus, Open‑Falcon.

ChatOps builds on existing tools and brings clear benefits:

Transparency: All work messages are stored in a single chat platform, visible to all members, eliminating communication barriers and preserving history.

Context sharing: Reduces message fragmentation caused by switching tools, ensuring complete information flow across roles and tools.

Mobile friendliness: Interact with backend tools via a chatbot on mobile without dealing with numerous complex interfaces.

DevOps culture promotion: Conversing with a bot lowers the entry barrier for DevOps practices, spreading automation across the team.

This article demonstrates how to use a DingTalk robot for ChatOps.

Adding a DingTalk Robot

Log in to the DingTalk developer console, navigate to Application Development > Enterprise Internal Development > Robot , and click Create Application .

Remember the AppKey and AppSecret:

Configure the server's outbound IP and message receiving URL:

Note: Configuring an HTTPS address requires a valid certificate.

Developing the Robot

HTTP HEADER

<code>{
  "Content-Type": "application/json; charset=utf-8",
  "timestamp": "1577262236757",
  "sign": "xxxxxxxxxx"
}</code>

Parameter

Description

timestamp

Message sending timestamp in milliseconds.

sign

Signature value.

Developers must verify the

timestamp

and

sign

in the header to ensure the request originates from DingTalk. Validation rules:

If the

timestamp

differs from the current system time by more than one hour, the request is considered illegal.

If the

sign

does not match the developer‑computed value, the request is illegal.

Both

timestamp

and

sign

must pass verification. The

sign

is calculated by concatenating

timestamp

, a newline, and the robot's

appSecret

, then applying HMAC‑SHA256 and Base64 encoding.

Below is a Go implementation:

<code>package main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "strconv"

    "github.com/gin-gonic/gin"
)

const (
    appSecret   = "xxx-xxx"
    baseHookUrl = "https://oapi.dingtalk.com/robot/send"
    accessToken = "xxx"
)

type incoming struct {
    MsgType string `json:"msgtype"`
    Text    *Text  `json:"text"`
    MsgId   string `json:"msgId"`
    CreateAt int64 `json:"createAt"`
    ConversationType string `json:"conversationType"`
    ConversationId   string `json:"conversationId"`
    ConversationTitle string `json:"conversationId"`
    SenderId   string `json:"senderId"`
    SenderNick string `json:"senderNick"`
    SenderCorpId string `json:"senderCorpId"`
    SenderStaffId string `json:"senderStaffId"`
    ChatbotUserId string `json:"chatbotUserId"`
    AtUsers []map[string]string `json:"atUsers"`
    SessionWebhook string `json:"sessionWebhook"`
    IsAdmin bool `json:"isAdmin"`
}

type Message struct {
    MsgType string `json:"msgtype"`
    At      At      `json:"at,omitempty"`
    Text    *Text   `json:"text,omitempty"`
    Markdown *Markdown `json:"markdown,omitempty"`
    Link    *Link   `json:"link,omitempty"`
}

type At struct {
    AtMobiles []string `json:"atMobiles,omitempty"`
    IsAtAll   bool     `json:"isAtAll,omitempty"`
}

type Markdown struct {
    Title string `json:"title,omitempty"`
    Text  string `json:"text,omitempty"`
}

type Text struct {
    Content string `json:"content,omitempty"`
}

type Link struct {
    Title      string `json:"title,omitempty"`
    Text       string `json:"text,omitempty"`
    MessageURL string `json:"messageURL,omitempty"`
    PicURL     string `json:"picURL,omitempty"`
}

func main() {
    r := gin.New()
    r.POST("/chatops", func(c *gin.Context) {
        var (
            sign string
            data []byte
        )
        httpSign := c.Request.Header.Get("Sign")
        httpTimestamp := c.Request.Header.Get("Timestamp")
        if tsi, err := strconv.ParseInt(httpTimestamp, 10, 64); err != nil {
            fmt.Println("Request header missing timestamp!")
        } else {
            data, _ = ioutil.ReadAll(c.Request.Body)
            sign = signature(tsi, appSecret)
        }
        if httpSign == sign {
            var body incoming
            if err := json.Unmarshal(data, &body); err != nil {
                fmt.Println(err)
                return
            }
            content := body.Text.Content
            fmt.Println(content)
            sendDingTalk("主人," + content)
        }
    })
    r.Run(":9000")
}

func signature(ts int64, secret string) string {
    strToSign := fmt.Sprintf("%d\n%s", ts, secret)
    hmac256 := hmac.New(sha256.New, []byte(secret))
    hmac256.Write([]byte(strToSign))
    data := hmac256.Sum(nil)
    return base64.StdEncoding.EncodeToString(data)
}

func sendDingTalk(content string) {
    msg := &Message{
        MsgType: "markdown",
        At:      At{},
        Markdown: &Markdown{
            Title: "消息测试",
            Text:  content,
        },
    }
    query := url.Values{}
    query.Set("access_token", accessToken)
    hookUrl, _ := url.Parse(baseHookUrl)
    hookUrl.RawQuery = query.Encode()
    msgContent, _ := json.Marshal(msg)
    req, err := http.NewRequest("POST", hookUrl.String(), bytes.NewReader(msgContent))
    if err != nil {
        fmt.Println(err)
    }
    req.Header.Set("Content-Type", "application/json; charset=utf-8")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }
    defer resp.Body.Close()
}
</code>

Deploy the service to a server and test the bot in a DingTalk group. The bot will respond to messages that match configured keywords.

Note: The DingTalk robot must be configured with specific keywords; only messages containing those keywords will trigger a response.

Documentation

DingTalk Robot API Documentation

automationoperationsGoChatOpsDingTalk
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.