接口概览
PushHub 提供简洁的 HTTP API,只需一行代码即可将消息推送到您已配置的渠道。支持企业微信、钉钉、飞书、Bark、PushPlus、Ntfy、邮件等多种推送方式。
https://pushhub.fengye.wang接口版本:V1 | 请求方式:GET / POST
接口特点
- ✅ 支持 GET 和 POST 两种请求方式
- ✅ 一个 Token 可绑定多个渠道,一次调用同时推送
- ✅ 按渠道成本扣费,不同渠道可设置不同成本倍数
- ✅ 异步队列投递,高并发下稳定可靠
- ✅ 支持幂等性 Key,24 小时内同一 Key 只处理一次
- ✅ 支持 Token 级 IP 白名单(按用户维度配置)
使用流程
在调用发送接口之前,需要完成渠道配置和 API Token 创建。整体流程如下:
调用接口
使用 Token 调用 /api/send,系统会自动将消息推送到 Token 绑定的所有可用渠道。
/api/send 时无需再指定渠道参数,发送目标由 Token 绑定的渠道决定。如需更换渠道,请在用户中心修改 Token 的渠道绑定。
认证方式
所有 API 请求都需要通过 API Token 进行认证。Token 可在用户中心 API Token 页面创建、刷新和管理。
方式一:URL 参数传递(推荐测试使用)
将 Token 作为 URL 的 token 查询参数:
https://pushhub.fengye.wang/api/send?token=YOUR_API_TOKEN&title=标题&content=内容
方式二:Authorization 请求头(推荐生产使用)
将 Token 放在 Authorization 请求头中,格式为 Bearer YOUR_API_TOKEN。此方式不会将 Token 记录在 URL 日志中,更加安全。
Authorization: Bearer YOUR_API_TOKEN
认证校验链
系统会按以下顺序对每次请求进行认证校验,任一环节失败都会立即返回错误:
- Token 存在性:检查请求中是否包含 Token
- Token 有效性:校验 Token 是否存在且未失效
- Token 状态:校验 Token 是否被禁用
- 用户状态:校验用户是否存在且未被禁用
- 会员有效期:校验用户会员是否过期
- IP 白名单:校验请求 IP 是否在白名单内(如已设置)
配额说明
系统采用配额 + 积分双轨制管理机制。每次调用 /api/send 时,会根据 Token 绑定的渠道计算本次调用成本,并从您的账户中预扣相应额度。
配额类型
| 类型 | 说明 | 重置周期 |
|---|---|---|
| 日配额 | 每天最多可调用次数 | 每天 0 点重置 |
| 月配额 | 每月最多可调用次数 | 每月 1 号重置 |
| 总配额 | 整个使用期间累计可调用次数 | 永不重置 |
| 积分 | 按量计费模式下的余额 | 充值或购买套餐后增加 |
渠道成本
不同渠道可以设置不同的成本倍数(cost_multiplier)。系统按渠道成本倍数扣费,而不是简单地按渠道数量扣费。
预扣与回滚机制
- 预扣:请求到达后先扣除配额/积分,防止超发
- 确认:消息成功投递后,预扣的额度正式消耗
- 回滚:发送失败或任务构建失败时,自动返还预扣额度
- 补偿:极端情况下回滚失败,会进入补偿队列异步处理,保证最终一致性
IP 白名单
IP 白名单用于限制哪些 IP 地址可以调用您的 API。白名单在用户维度配置,对该用户下所有 Token 生效。
生效规则
- 未设置白名单(为空)时,允许任意 IP 访问
- 设置白名单后,仅允许列表中的 IP 调用 API
- 支持 IPv4,暂不支持 CIDR 网段
- 多个 IP 请用换行或逗号分隔
如何获取当前 IP
您可以通过以下命令查看当前出口 IP:
直接访问 https://ip138.com/
发送消息
通过此接口将消息推送到指定 API Token 绑定的所有可用渠道。默认采用异步队列投递,成功入队后立即返回任务 ID,实际发送由后台 Worker 异步完成。
请求地址
请求参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| token | string | 是 | API Token。也可通过 Authorization: Bearer 请求头传递 |
| title | string | 否 | 消息标题,最多 100 字符 |
| content | string | 是 | 消息内容,最多 500 字符 |
| idempotency_key | string | 否 | 幂等性 Key,24 小时内同一 Key 只处理一次 |
/api/send 时无需指定渠道参数,系统会自动将消息发送到该 Token 绑定的所有已配置且已启用渠道。
响应参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| status | boolean | 请求是否成功 |
| code | int | 状态码,200 表示成功 |
| msg | string | 响应消息 |
| data.job_id | string | 任务唯一 ID,可用于追踪发送状态 |
| data.status | string | 任务状态,通常为 queued(已入队) |
| data.channel_count | int | 本次发送涉及的渠道数量 |
| data.total_cost | int | 本次调用总成本(按渠道成本倍数合计) |
| data.cost_details | array | 各渠道成本明细 |
| data.remaining_quota | object | 剩余配额信息,包含 daily、points 等 |
| data.deduct_info | object | 扣费详情:pre_deduct_type、total_cost、quota_count、points_count |
| data.token_sync | object | 配额同步状态 |
| data.response_time_ms | int | 接口响应耗时(毫秒) |
成功响应示例
{
"status": true,
"code": 200,
"msg": "已加入发送队列",
"data": {
"job_id": "job_64a8b2c9e4f1a",
"status": "queued",
"channel_count": 2,
"total_cost": 3,
"cost_details": [
{ "channel_name": "钉钉Webhook", "cost_multiplier": 1 },
{ "channel_name": "飞书Webhook", "cost_multiplier": 2 }
],
"response_time_ms": 12,
"remaining_quota": {
"daily": 97,
"monthly": -1,
"total": -1,
"points": 500
},
"deduct_info": {
"pre_deduct_type": "quota",
"total_cost": 3,
"quota_count": 3,
"points_count": 0
},
"token_sync": {
"threshold_enabled": true,
"threshold": 10,
"current_count": 3,
"remaining": 7,
"will_sync": false,
"message": "还差 7 次同步到数据库"
}
},
"time": 1774967730
}
降级同步响应示例
当 Redis 队列满或异常时,系统会降级为同步发送,并返回各渠道的实际发送结果:
{
"status": true,
"code": 200,
"msg": "发送成功",
"data": {
"success_count": 1,
"total_count": 1,
"results": {
"wecom": {
"status": true,
"msg": "发送成功"
}
},
"response_time_ms": 156
},
"time": 1774967730
}
错误响应示例
{
"status": false,
"code": 201002,
"msg": "Token不存在或已失效",
"data": [],
"time": 1774966976
}
HTTP 状态码说明
| 状态码 | 说明 |
|---|---|
| 200 | 请求成功(已入队或同步发送成功) |
| 400 | 请求参数错误 |
| 401 | Token 缺失、无效或已停用 |
| 403 | 无权限访问(IP 不在白名单或会员已过期) |
| 429 | 配额已用完或请求过于频繁 |
| 500 | 服务器内部错误 |
详细错误码
系统采用统一的错误码体系,格式为 A-BB-CCC:
- A: 系统标识 (1=系统级, 2=业务级, 3=第三方)
- BB: 模块标识 (00=通用, 01=认证, 02=配额, 03=渠道, 04=队列, 05=消息, 06=幂等性)
- CCC: 具体错误序号
系统级错误 (1-xx-xxx)
| 错误码 | 说明 | 解决建议 |
|---|---|---|
| 100001 | 系统繁忙 | 请稍后重试 |
| 100003 | 请求过于频繁 | 降低请求频率,建议 60 秒后再试 |
| 100004 | 请求参数错误 | 检查请求参数是否符合规范 |
认证模块错误 (2-01-xxx)
| 错误码 | 说明 | 解决建议 |
|---|---|---|
| 201001 | 缺少 Token 参数 | 在 URL 中添加 token 参数或 Authorization 请求头 |
| 201002 | Token 不存在或已失效 | 检查 Token 是否正确,或重新创建 Token |
| 201003 | Token 已停用 | 在用户中心启用该 Token |
| 201005 | 用户不存在 | 联系管理员检查账号状态 |
| 201006 | 用户已被禁用 | 联系管理员解除禁用 |
| 201007 | 会员已过期 | 及时续费恢复会员服务 |
| 201008 | IP 不在白名单中 | 将当前 IP 添加到白名单,或关闭 IP 白名单 |
配额模块错误 (2-02-xxx)
| 错误码 | 说明 | 解决建议 |
|---|---|---|
| 202001 | 日配额已用完 | 等待次日 0 点重置,或升级套餐 |
| 202002 | 月配额已用完 | 等待次月 1 号重置,或升级套餐 |
| 202003 | 总配额已用完 | 升级套餐获取更多配额 |
| 202005 | 配额预扣失败 | 检查账户配额/积分是否充足 |
| 202006 | 积分不足 | 充值积分或购买套餐 |
渠道模块错误 (2-03-xxx)
| 错误码 | 说明 | 解决建议 |
|---|---|---|
| 203001 | Token 未绑定任何可用渠道 | 在 "API Token" 页面绑定推送渠道 |
| 203002 | 渠道已停用 | 在 "渠道配置" 页面启用渠道 |
| 203003 | 渠道配置错误 | 检查渠道配置参数是否正确 |
| 203005 | 所有渠道发送失败 | 检查各渠道配置和网络状态 |
消息内容错误 (2-05-xxx)
| 错误码 | 说明 | 解决建议 |
|---|---|---|
| 205003 | 消息内容不能为空 | 添加 content 参数 |
| 205004 | 消息内容超过 500 字符 | 缩短消息内容长度 |
| 205005 | 消息内容包含非法字符 | 移除特殊字符或 HTML 标签 |
高级功能
幂等性请求
为防止重复发送,可以使用幂等性 Key。相同 Key 在 24 小时内只会处理一次,适用于网络超时重试、用户快速双击等场景。
# 添加 idempotency_key 参数
curl "https://pushhub.fengye.wang/api/send?token=YOUR_TOKEN&idempotency_key=order_12345&title=订单通知&content=您有新订单"
请求限流
系统采用 Token + IP 组合限流,防止代理 IP 绕过。单个 Token 在限制周期内超过配额后将返回 429 错误。
retry_after 等待后重试。
cURL 示例
GET 方式(URL 传 Token)
curl "https://pushhub.fengye.wang/api/send?token=YOUR_TOKEN&title=测试标题&content=测试内容"
GET 方式(Authorization 请求头)
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://pushhub.fengye.wang/api/send?title=测试标题&content=测试内容"
POST 方式
curl -X POST "https://pushhub.fengye.wang/api/send" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d "title=测试标题" \
-d "content=测试内容"
PHP 示例
GET 方式
<?php
$token = 'YOUR_TOKEN';
$title = urlencode('告警通知');
$content = urlencode('服务器异常,请尽快处理');
$url = 'https://pushhub.fengye.wang/api/send?token=' . $token . '&title=' . $title . '&content=' . $content;
$response = file_get_contents($url);
$result = json_decode($response, true);
if ($result['status']) {
echo "已入队,任务ID:" . $result['data']['job_id'];
echo "剩余日配额:" . $result['data']['remaining_quota']['daily'];
} else {
echo "发送失败:" . $result['msg'];
}
POST 方式(推荐)
<?php
$token = 'YOUR_TOKEN';
$data = [
'title' => '告警通知',
'content' => '服务器异常,请尽快处理',
];
$options = [
'http' => [
'method' => 'POST',
'header' => 'Authorization: Bearer ' . $token . "\r\n" .
"Content-Type: application/x-www-form-urlencoded\r\n",
'content' => http_build_query($data),
],
];
$response = file_get_contents('https://pushhub.fengye.wang/api/send', false, stream_context_create($options));
$result = json_decode($response, true);
print_r($result);
Python 示例
GET 方式
import requests
import urllib.parse
token = 'YOUR_TOKEN'
title = urllib.parse.quote('系统告警')
content = urllib.parse.quote('磁盘空间不足,请及时清理')
url = f"https://pushhub.fengye.wang/api/send?token={token}&title={title}&content={content}"
response = requests.get(url)
result = response.json()
if result['status']:
print(f"已入队:{result['data']['job_id']}")
print(f"渠道数:{result['data']['channel_count']}")
print(f"总成本:{result['data']['total_cost']}")
print(f"剩余日配额:{result['data']['remaining_quota']['daily']}")
else:
print(f"发送失败:{result['msg']}")
POST 方式(推荐)
import requests
token = 'YOUR_TOKEN'
url = f"https://pushhub.fengye.wang/api/send"
headers = {
'Authorization': f'Bearer {token}',
}
data = {
'title': '系统告警',
'content': '磁盘空间不足,请及时清理',
'idempotency_key': 'alert_20250101_001',
}
response = requests.post(url, headers=headers, data=data)
print(response.json())
JavaScript 示例
Node.js / 服务端调用
const token = 'YOUR_TOKEN';
const title = encodeURIComponent('新订单通知');
const content = encodeURIComponent('您有新的订单,请及时处理');
const url = `https://pushhub.fengye.wang/api/send?token=${token}&title=${title}&content=${content}`;
fetch(url)
.then(response => response.json())
.then(data => {
if (data.status) {
console.log('已入队:', data.data.job_id);
console.log('渠道数:', data.data.channel_count);
console.log('总成本:', data.data.total_cost);
console.log('剩余日配额:', data.data.remaining_quota.daily);
} else {
console.error('发送失败:', data.msg);
}
})
.catch(error => {
console.error('请求错误:', error);
});
浏览器端(通过后端转发)
// 浏览器端请勿直接暴露 Token,应通过您自己的后端转发
fetch('/your-backend-api/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: '新订单通知',
content: '您有新的订单,请及时处理'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('请求错误:', error));