主题
Webhook SSL证书配置
前面那些部署方式都是 yunplus 替你去操作某个具体产品。Webhook 反过来:yunplus 只负责在证书更新时把证书内容发给你,剩下的事情你自己决定怎么做。
适合这些情况:
- 部署目标是 yunplus 暂未直接支持的产品或自建系统
- 公司内部已有证书分发流程,只需要一个触发源
- 想在证书更新时顺便做点别的事,比如通知群、写审计日志、触发 CI
配置有多简单
只有一个字段:请求地址。

没有密钥、没有签名配置、没有请求方式选择——这些都是固定的,见下一节。
请求规格
| 项目 | 值 |
|---|---|
| 请求方法 | POST |
| Content-Type | application/json |
| 请求地址 | 你在表单里填的地址 |
| 触发时机 | 每次证书签发成功后 |
请求体是证书对象,包含这些字段:
| 字段 | 类型 | 含义 |
|---|---|---|
id | number | 证书在 yunplus 中的 ID |
name | string | 证书名称 / 域名 |
pem | string | 证书公钥内容(PEM 格式,含证书链) |
privateKey | string | 证书私钥内容(PEM 格式) |
大致形如:
json
{
"id": 12345,
"name": "*.example.com",
"pem": "-----BEGIN CERTIFICATE-----\nMIIF...\n-----END CERTIFICATE-----\n",
"privateKey": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n"
}第一次接入建议先把原始请求体打出来
用一个只做日志的接口先接一次,把收到的完整 JSON 打印出来存下,再按实际结构写解析逻辑。这样最稳妥,也方便日后排查。
整体流程
操作步骤
- 先把接收端服务部署好,确保能从公网访问。
- 进入 证书配置列表,点击目标证书的「自动部署」。
- 点击「新增自动部署」,在选择器左侧点「其他」,选择「Webhook」。

- 填入请求地址,点击「提交」。系统会立刻用当前证书触发一次,可以直接验证接口是否正常。
接收端示例
Node.js(Express)
javascript
const express = require('express');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const app = express();
app.use(express.json({ limit: '2mb' }));
// 用一个只有你知道的随机路径,避免被扫到
app.post('/hooks/cert/8f3a2b1c', (req, res) => {
const { id, name, pem, privateKey } = req.body;
if (!pem || !privateKey) {
return res.status(400).json({ ok: false, msg: 'missing cert content' });
}
// 域名可能是 *.example.com,星号不能直接做文件名
const safeName = name.replace(/\*/g, '_');
const dir = '/etc/nginx/certs';
fs.writeFileSync(path.join(dir, `${safeName}.pem`), pem, { mode: 0o644 });
fs.writeFileSync(path.join(dir, `${safeName}.key`), privateKey, { mode: 0o600 });
execSync('nginx -t && nginx -s reload');
console.log(`[cert] updated ${name} (id=${id})`);
res.json({ ok: true });
});
app.listen(3000);Python(Flask)
python
import os
import subprocess
from flask import Flask, request, jsonify
app = Flask(__name__)
CERT_DIR = '/etc/nginx/certs'
@app.route('/hooks/cert/8f3a2b1c', methods=['POST'])
def receive_cert():
data = request.get_json(silent=True) or {}
pem = data.get('pem')
private_key = data.get('privateKey')
name = data.get('name', '')
if not pem or not private_key:
return jsonify(ok=False, msg='missing cert content'), 400
safe_name = name.replace('*', '_')
with open(os.path.join(CERT_DIR, f'{safe_name}.pem'), 'w') as f:
f.write(pem)
key_path = os.path.join(CERT_DIR, f'{safe_name}.key')
with open(key_path, 'w') as f:
f.write(private_key)
os.chmod(key_path, 0o600)
subprocess.run(['nginx', '-t'], check=True)
subprocess.run(['nginx', '-s', 'reload'], check=True)
app.logger.info('cert updated: %s', name)
return jsonify(ok=True)
if __name__ == '__main__':
app.run(port=3000)安全建议
私钥通过 HTTP 传输,接收端的安全性完全由你自己保证。
这几件事一定要做
- 只用 HTTPS 地址。明文 HTTP 传私钥等于把私钥公开在链路上。
- 地址里带一段足够长的随机路径,比如
/hooks/cert/8f3a2b1c9d4e,相当于一个共享密钥,避免被扫描器发现。 - 校验请求体,
pem或privateKey缺失时直接返回错误,不要写入残缺文件。 - 私钥文件权限设为 600,且不要放在 Web 可访问的目录下。
- 不要把私钥内容写进日志。日志里记域名和证书 ID 就够了。
有条件的话,再加一层来源 IP 限制或反向代理层的鉴权会更稳。
验证与排查
配置提交后,自动部署列表里会显示这个任务的执行状态。
接口没被调用:确认地址填写完整(含 https:// 和路径)、服务能从公网访问、防火墙放行了对应端口。
接口被调用但报错:先看你自己服务的日志。常见的是 body 解析失败(没配 JSON 解析中间件)、或请求体超过了默认大小限制(证书链内容可能有几 KB,示例里设了 2mb)。
写入成功但网站还是旧证书:检查 nginx -s reload 是否真的执行了。可以在接收端把 reload 的输出也记进日志。
想手动触发一次做测试:在证书列表里用「手动生成证书」签发一张新证书,会连带触发一遍部署任务。注意这会消耗一张剩余证书额度,见 证书订单与下载。
