Background
We want to notify a DingTalk robot via webhook after each GitLab Runner execution, informing users about what the runner did and which commit version was used. The desired effect is shown in the image below:
Write the Code to Call the DingTalk Webhook
Refer to DingTalk’s official Python code:
#!/usr/bin/env python
import argparse
import logging
import time
import hmac
import hashlib
import base64
import urllib.parse
import requests
def setup_logger():
logger = logging.getLogger()
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter('%(asctime)s %(name)-8s %(levelname)-8s %(message)s [%(filename)s:%(lineno)d]'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
def define_options():
parser = argparse.ArgumentParser()
parser.add_argument(
'--access_token', dest='access_token', required=True,
help='The robot webhook access_token from https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot '
)
parser.add_argument(
'--secret', dest='secret', required=True,
help='The secret from https://open.dingtalk.com/document/orgapp/customize-robot-security-settings#title-7fs-kgs-36x'
)
parser.add_argument(
'--userid', dest='userid',
help='DingTalk user ID(s) to @ (comma-separated), from https://open.dingtalk.com/document/orgapp/basic-concepts-beta#title-o8w-yj2-t8x '
)
parser.add_argument(
'--at_mobiles', dest='at_mobiles',
help='Mobile number(s) to @ (comma-separated)'
)
parser.add_argument(
'--is_at_all', dest='is_at_all', action='store_true',
help='Whether to @ everyone; if specified, it is True; otherwise, False'
)
parser.add_argument(
'--msg', dest='msg', default='DingTalk: Empowering Progress',
help='Message content to send'
)
return parser.parse_args()
def send_custom_robot_group_message(access_token, secret, msg, at_user_ids=None, at_mobiles=None, is_at_all=False):
"""
Send a custom DingTalk robot group message.
:param access_token: Robot webhook access_token
:param secret: Signing secret from robot security settings
:param msg: Message content
:param at_user_ids: List of user IDs to @
:param at_mobiles: List of mobile numbers to @
:param is_at_all: Whether to @ everyone
:return: DingTalk API response
"""
timestamp = str(round(time.time() * 1000))
string_to_sign = f'{timestamp}\\n{secret}'
hmac_code = hmac.new(secret.encode('utf-8'), string_to_sign.encode('utf-8'), digestmod=hashlib.sha256).digest()
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
url = f'https://oapi.dingtalk.com/robot/send?access_token={access_token}×tamp={timestamp}&sign={sign}'
body = {
"at": {
"isAtAll": str(is_at_all).lower(),
"atUserIds": at_user_ids or [],
"atMobiles": at_mobiles or []
},
"text": {
"content": msg
},
"msgtype": "text"
}
headers = {'Content-Type': 'application/json'}
resp = requests.post(url, json=body, headers=headers)
logging.info("DingTalk custom robot group message response: %s", resp.text)
return resp.json()
def main():
options = define_options()
# Process @ user IDs
at_user_ids = []
if options.userid:
at_user_ids = [u.strip() for u in options.userid.split(',') if u.strip()]
# Process @ mobile numbers
at_mobiles = []
if options.at_mobiles:
at_mobiles = [m.strip() for m in options.at_mobiles.split(',') if m.strip()]
send_custom_robot_group_message(
options.access_token,
options.secret,
options.msg,
at_user_ids=at_user_ids,
at_mobiles=at_mobiles,
is_at_all=options.is_at_all
)
if __name__ == '__main__':
main()
Write a Python Script to Fulfill Our Requirements
import os
import sys
import json
import time
import hmac
import hashlib
import base64
import urllib.parse
import urllib.request
def main():
webhook = os.environ.get("DINGTALK_WEBHOOK", "")
if not webhook:
print("DINGTALK_WEBHOOK not set, skipping notification.")
return 0
secret = os.environ.get("DINGTALK_SECRET", "")
branch = os.environ.get("CI_COMMIT_REF_NAME", "")
commit_sha = os.environ.get("CI_COMMIT_SHORT_SHA", "")
commit_msg = os.environ.get("CI_COMMIT_MESSAGE", "").strip()
project_url = 'Your GitLab URL'
commit_full_sha = os.environ.get("CI_COMMIT_SHA", "")
commit_link = f"{project_url}/-/commit/{commit_full_sha}"
content = f"Deployment completed successfully.\nBranch used: {branch}\nCommit SHA: {commit_sha}\nCommit message: {commit_msg}\nCommit link: {commit_link}"
payload = {"msgtype": "text", "text": {"content": content}}
data = json.dumps(payload).encode("utf-8")
url = webhook
if secret:
ts = str(int(time.time() * 1000))
string_to_sign = f"{ts}\\n{secret}"
h = hmac.new(secret.encode("utf-8"), string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest()
sign = urllib.parse.quote_plus(base64.b64encode(h).decode("utf-8"))
url = f"{webhook}×tamp={ts}&sign={sign}"
try:
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=10) as f:
body = f.read().decode("utf-8")
print("DingTalk notify success:", body)
return 0
except Exception as e:
print("DingTalk notify failed:", e, file=sys.stderr)
return 2
if __name__ == "__main__":
sys.exit(main())
Invoke This Python Script in GitLab Runner
stages:
- notify
notify-success:
stage: notify
image: python:3.11-alpine
only:
- dev
script:
- |
# Send deployment success notification via DingTalk custom robot (using script in the project)
python3 scripts/ci_dingtalk_notify.py
Configure Variables in GitLab CI/CD
Conclusion
Run!

