Webhook
在你的服务器上实时接收激活事件,验证 SmsGrab-Signature 请求头,并了解重试、测试推送和自动停用。
Webhook 会在事件发生时立即推送到你的服务器,你无需反复查询。一个 Webhook 属于一个 API 密钥。它接收用该密钥购买的号码的事件,以及你账号的余额不足提醒。
事件
| 事件 | 触发时机 |
|---|---|
activation.created |
购买了号码 |
activation.code_received |
每条新短信,附带目前为止的所有消息 |
activation.completed |
由你完成,或收到验证码后时间结束 |
activation.cancelled |
在任何验证码前取消,已退款 |
activation.expired |
未及时收到验证码,已退款 |
activation.refunded |
每笔扣款退款 |
balance.low |
余额降到你设置的阈值以下 |
webhook.test |
你发起的测试推送 |
创建 Webhook
在 账号 > 开发者 中打开一个密钥,填写 URL、需要的事件和可选的描述,即可添加 Webhook。每个密钥最多可以有 3 个 Webhook。
URL 必须:
- 以
https://开头; - 使用主机名,而不是 IP 地址;
- 指向公网地址。私有网络、localhost 以及类似网段会被拒绝。
你会收到一个以 whsec_ 开头的签名密钥。与 API 密钥一样,它只显示一次。你之后可以更换它,更换时同样需要输入密码。
推送格式
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: SmsGrab-Webhooks/1.0
SmsGrab-Event: activation.code_received
SmsGrab-Delivery: 9f7d6c1e-2b44-4c1a-8f0e-6a3d2c1b0e9f
SmsGrab-Signature: t=1790503200,v1=3b9a0c…
{ "id": "evt_4e1f…", "type": "activation.code_received", "created_at": "2026-09-27T10:06:12.004Z",
"api_version": "2026-09-27", "data": { "activation": { "id": "5c1d0c2e-…", "status": "CODE_RECEIVED" } } }
验证签名
SmsGrab-Signature 包含时间戳 t 和 v1。v1 是以你的签名密钥为密钥,对 t、一个句点和原始请求体拼接后计算的 HMAC-SHA256。请拒绝时间戳与你的时钟相差超过 300 秒的推送,并以恒定时间比较签名。
[$t, $v1] = (function (string $h): array {
parse_str(str_replace(',', '&', $h), $p);
return [(int) ($p['t'] ?? 0), (string) ($p['v1'] ?? '')];
})($_SERVER['HTTP_SMSGRAB_SIGNATURE'] ?? '');
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $t.'.'.$body, getenv('SMSGRAB_WEBHOOK_SECRET'));
$ok = abs(time() - $t) <= 300 && hash_equals($expected, $v1);
import hmac, hashlib, time
def verify(header: str, body: bytes, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return abs(time.time() - t) <= 300 and hmac.compare_digest(expected, parts.get("v1", ""))
import crypto from 'node:crypto';
export function verify(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t ?? 0);
const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex');
const given = Buffer.from(parts.v1 ?? '', 'utf8');
return Math.abs(Date.now() / 1000 - t) <= 300 && given.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), given);
}
请始终在解析 JSON 之前,用收到的原始请求体进行验证。
快速响应
请在 10 秒内 返回任意 2xx 状态,耗时的工作之后再处理,例如放入队列。不会跟随重定向。
重试
推送失败后,会在 1 分钟、5 分钟、15 分钟、1 小时、3 小时、6 小时、12 小时和 24 小时后重试。重试会携带相同的事件 id,因此请保存已处理的 ID 并跳过重复推送。同一激活的事件会按顺序到达。
自动停用
- 返回
410 Gone会立即停用 Webhook。 - 在至少 24 小时内连续失败 50 次后,Webhook 会被停用,你会收到通知。修复你的接口后,请重新启用 Webhook。
- 吊销密钥会停用其 Webhook。
测试与重新推送
在 Webhook 页面发送 webhook.test 事件,即可立即看到状态码、响应时间和任何错误。推送记录会保存每次推送 30 天,你可以重新推送其中任意一条。
这篇文章对您有帮助吗?
谢谢!很高兴能帮到您。