GitHub Webhook 签名校验总失败,主因是 req.Body 被提前读空;必须在 handler 开头用 io.ReadAll 一次性读取并缓存 payload,后续用 bytes.NewReader 重建 body,再以 os.Getenv("WEBHOOK_SECRET") 计算 HMAC-SHA256 并用 bytes.Equal 安全校验,失败统一返回 http.StatusForbidden。

GitHub Webhook 签名校验为什么总失败
直接用 http.HandleFunc 接收 GitHub 请求却始终返回 403,大概率是签名比对失败——不是密钥错了,而是 req.Body 被提前读空了。GitHub 的 X-Hub-Signature-256 必须用原始 payload 计算 HMAC-SHA256,而 Go 的 req.Body 是单次读取流。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在 handler 开头立刻调用
io.ReadAll(req.Body),存为payload字节切片 - 后续解析 JSON 时,用
bytes.NewReader(payload)重建 body,避免二次读空 - 校验时用
hmac.NewSHA256+os.Getenv("WEBHOOK_SECRET")计算,再用bytes.Equal比对(防时序攻击) - 失败一律返回
http.StatusForbidden,不输出具体错误原因
GitLab 和 GitHub 的签名逻辑能混用吗
不能。GitHub 用 X-Hub-Signature-256 做 HMAC 校验,GitLab 直接比对明文 X-Gitlab-Token,二者 header 名、算法、校验方式完全不同。混写一套逻辑会导致其中一方永远拒绝请求。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 按请求来源区分处理:检查
req.Header.Get("User-Agent")是否含GitLab,或统一用路由前缀(如/github-webhookvs/gitlab-webhook) - GitLab token 比对必须严格大小写敏感,且不能
strings.TrimSpace——空格是合法 token 组成部分 - 所有 webhook 路由只接受
POST,且必须校验Content-Type: application/json
执行 git pull 和 systemctl restart 为什么权限总被拒
Webhook 服务常以低权限用户(如 deploy)运行,但 git pull 需 SSH key 访问私有仓库,systemctl restart 默认需 root 权限。硬编码凭据或直接用 exec.Command 调用会触发安全拦截或静默失败。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 部署目录 owner 设为
deploy:deploy,SSH key 放在/home/deploy/.ssh/,配好~/.ssh/config绑定 Host - 用
sudoers限定授权:deploy ALL=(root) NOPASSWD: /bin/systemctl restart myapp.service - 命令执行前强制
os.Chdir("/var/www/myapp"),避免 cwd 错误导致git污染其他项目 - 加超时控制:
cmd.WaitDelay = 30 * time.Second,防止卡死进程
本地调试 Webhook 为什么 curl 总是 400
本地没公网 IP,GitHub/GitLab 根本发不出请求;更常见的是手动 curl 模拟时漏掉关键 header 或 body 格式不对,导致 Go 服务解析失败或跳过签名校验。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
smee.io转发:启动smee -u https://smee.io/xxx -t http://localhost:8080/webhook,再在 GitHub 设置里填该 URL -
curl测试必须带:-H "Content-Type: application/json" -H "X-Hub-Signature-256: sha256=xxx",payload 用真实推送的 JSON - handler 开头加
log.Printf("raw body: %s", string(payload)),确认收到的是合法 JSON,而非 Nginx 的 404 HTML 页面 - GitLab “Test” 按钮发的是简化 payload,真流程必须
git push触发
实际部署时最容易被忽略的是 http.Server 超时配置——没设 ReadTimeout 和 IdleTimeout,一个慢连接就能让 goroutine 堆积,最终耗尽内存或文件句柄。


















