NoKYCPhone

开发者

十分钟内使用 Webhook
实现短信自动化。

每五秒轮询一次收件箱,只能工作到它失灵为止。下面是完成同一任务的正确方式:用代码订购号码、注册 Webhook、验证签名,并编写一个能安全应对重复投递的处理程序。

  • 9 分钟阅读
  • 更新于 2026年5月21日
  • 无需注册即可阅读
一个绿色数据包在玻璃面板与服务器机架之间划出弧线,后方还有几道更淡的重复弧线

开始之前

准备三样东西,五分钟即可。

  1. 账户和余额。创建访问密钥,用加密货币充值。订购号码会扣除余额,因此账户中必须有钱。
  2. 一个具有 admin 权限范围的 API 密钥。订购号码会产生费用,因此 readwrite 密钥无权操作。请在 API 密钥页面创建;密钥只显示一次。
  3. 一个我们能够访问的 HTTPS 端点。纯 HTTP 会被拒绝。本地开发时,任何隧道工具都可以使用。

不要把密钥放进代码仓库。 admin 密钥可以持续订购号码,直到余额耗尽。请使用环境变量、密钥管理器或其他安全方式,绝不能提交到仓库;如果密钥泄露,应先在控制面板中撤销,再做其他处理。

第 1 步——订购号码

只需一次调用。系统会扣除余额,并在分配确认后返回结果;号码约一分钟内开始接收消息。

POST /v1/numbers
curl -X POST https://api.nokycphone.com/v1/numbers \
  -H "Authorization: Bearer $NKP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"country":"de","type":"mobile","period":1,"label":"signup-bot"}'

# 201 Created
{
  "id": "num_8f2c1a94",
  "e164": "+4915735550088",
  "status": "active",
  "renews_at": "2026-10-21T09:14:02Z",
  "charged": { "monthly": 12.90, "activation": 10.00, "total": 22.90 }
}

始终发送 Idempotency-Key。 在你的系统看来,请求超时和请求失败无法区分。带上此标头后,重试会重放已保存的响应,而不会再订购第二个号码;该记录会保留 24 小时。

第 2 步——注册 Webhook

每个端点只需注册一次,之后便可停止轮询。密钥只会在这次响应中返回,以后不再显示,因此请立即保存。

POST /v1/webhooks
curl -X POST https://api.nokycphone.com/v1/webhooks \
  -H "Authorization: Bearer $NKP_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.org/hooks/nkp","events":["message.received"]}'

# 201 Created
{
  "id": "whk_77c1e0",
  "url": "https://example.org/hooks/nkp",
  "events": ["message.received"],
  "secret": "whsec_2b91f4c7a0d3…"
}

只订阅你确实会处理的事件。完成这项任务时,需要了解以下事件:

  • message.received——收到一条短信,验证码已自动提取。
  • number.active——订购的号码已经启用。
  • number.grace——余额不足导致续费失败。请为此事件设置警报;它会在号码消失前提前 3 天发出警告。
  • topup.credited——充值已确认到账。

第 3 步——验证签名

如果一个端点接受发送给它的任何内容,那就不是 Webhook,而是一个公开的写入 API。信任请求正文前,必须先验证签名。

签名以端点密钥为键,对 timestamp + "." + raw_body 计算 HMAC-SHA256,再编码为十六进制;它位于 X-NKP-Signature 标头中,格式为 v1=…

handler.py
import hmac, hashlib, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["NKP_WEBHOOK_SECRET"].encode()
seen = set()   # en production : Redis, avec une expiration

@app.post("/hooks/nkp")
def hook():
    raw = request.get_data()                      # le corps BRUT, jamais re-serialise
    ts  = request.headers.get("X-NKP-Timestamp", "")
    sig = request.headers.get("X-NKP-Signature", "").split("=", 1)[-1]

    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        abort(400)                                 # trop vieux : rejeu
    mine = hmac.new(SECRET, f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(mine, sig):
        abort(401)

    delivery = request.headers["X-NKP-Delivery"]
    if delivery in seen:
        return "", 200                             # deja traite : on acquitte quand meme
    seen.add(delivery)

    event = request.get_json()
    if event["event"] == "message.received":
        enqueue(event["data"])                     # traiter APRES avoir repondu
    return "", 200

这个处理程序中有三件事至关重要;漏掉任何一项,都可能造成真实的服务中断:

  • 对原始正文签名。解析 JSON 后重新序列化会改变字节,导致 HMAC 校验失败。
  • 使用恒定时间比较。== 比较签名会泄露时序信息。
  • 拒绝过旧的时间戳。如果没有五分钟窗口,截获的请求就能被无限重放。

你会收到什么

向你的端点发送 POST
POST /hooks/nkp HTTP/1.1
X-NKP-Event: message.received
X-NKP-Delivery: evt_6f0b28d4
X-NKP-Timestamp: 1789033541
X-NKP-Signature: v1=6b3a1f8e7c0d94aa5e2b…
Content-Type: application/json

{
  "event": "message.received",
  "created_at": "2026-09-21T09:45:41Z",
  "data": {
    "id": "msg_5b1f77c0",
    "number": "num_8f2c1a94",
    "e164": "+4915735550088",
    "from": "Telegram",
    "body": "Your login code is 72194. Do not share it.",
    "code": "72194",
    "received_at": "2026-09-21T09:45:41Z"
  }
}

code 由服务器按照控制面板所用的同一套规则提取:识别 4 至 8 位数字组,去掉空格和连字符,并忽略发件人自己的号码。如果没有内容符合验证码特征,该字段为 null,而 body 仍会保留,供你自行解析。

重试、顺序及其他容易踩坑之处

  • 采用至少一次投递。请根据 X-NKP-Delivery 去重。同一个 ID 永远不会代表两个不同事件。
  • 不保证顺序。间隔一秒的两条消息可能以任意顺序到达。如果顺序很重要,请按 created_at 排序。
  • 必须在十秒内响应。处理程序响应更慢会被视为失败,事件将重新投递。应先确认接收,再执行后续工作。
  • 大约一天内尝试八次,退避间隔依次为 10 秒、1 分钟、5 分钟、30 分钟、2 小时、6 小时、12 小时和 24 小时。
  • 连续三天全部失败会禁用端点,控制面板会明确提示。数据不会丢失:仍可通过 API 读取消息。

保留轮询作为后备方案。 Webhook 是快速通道,而不是事实数据源。每隔几分钟执行一次轻量的 GET /v1/messages?since=…,可以补回端点停机期间漏掉的内容;没有新内容时也不会产生额外成本。

第 4 步——让自动化持续健康运行

如果自动化程序只订购号码却从不释放,余额会在不知不觉中耗尽。请养成两个习惯:

  • 释放已经用完的号码。DELETE /v1/numbers/{id} 会立即生效并停止续费。消息会随之删除,因此请先读取。
  • 监控 number.grace这是号码自动释放前唯一的警告,会提前 3 天发出。
sweep.sh
# release every line labelled "signup-bot" that has been idle for a week
curl -s "https://api.nokycphone.com/v1/numbers?label=signup-bot" \
     -H "Authorization: Bearer $NKP_KEY" |
jq -r '.data[] | select(.last_message_at < (now - 604800 | todate)) | .id' |
while read -r id; do
  curl -s -X DELETE "https://api.nokycphone.com/v1/numbers/$id" \
       -H "Authorization: Bearer $NKP_KEY"
done

每个密钥的速率限制为每分钟读取 120 次、写入 30 次,订购操作另有单独上限。每个响应都带有 X-RateLimit-Remaining;收到 429 时,应遵循 Retry-After,不要继续猛发请求。完整数值见 API 参考文档

常见问题

我需要使用 Webhook,还是轮询就够了?

规模较小时,轮询完全可用,而且更容易调试。Webhook 会在一秒内送达,而不用等到下一次轮询;没有事件时也没有成本。多数人最终会两者并用:Webhook 保证速度,低频轮询提供安全网。

为什么我的签名验证总是失败?

最常见的原因是正文在计算哈希前已经被解析并重新序列化。必须严格按收到时的原始字节计算签名。第二常见的原因是只对正文计算哈希,而没有依次加入时间戳、一个英文句点和正文。

可以为每个号码设置不同的 Webhook 吗?

可以。通过 PATCH 在号码上设置的专属 Webhook URL 会覆盖账户级端点;这是把不同自动化任务路由到不同服务的简洁方式。

如果我的服务器停机会怎样?

我们会在约一天内重试八次。连续三天全部失败后,端点会被禁用,控制面板会明确提示。无论如何消息都不会丢失,仍可通过 API 读取。

有官方 SDK 吗?

没有,这是有意为之。接口规模很小,封装库反而会比 HTTP 调用更快过时。API 参考文档中提供了完整的 Node 和 Python 客户端示例,各自大约十五行。


本文由运营本服务的团队撰写,更新于 2026年5月21日。如果其中有任何错误或过时内容,请告诉我们——我们更新指南的速度比修复代码更快。

四次调用,
此后自动运行。

在控制面板中创建密钥,再用自己的代码订购第一个号码。整个过程无需提交任何证件。