SmsEmpire API
在你自己的代码中购买短信和邮箱验证,从账户余额扣费,并享受低于网站价格的折扣。
- →通过 API 的每一笔购买都享受折扣价
- →验证码到达即发送带签名的 Webhook —— 无需轮询
- →幂等下单:网络超时后重试是安全的
快速开始
所有接口都在 https://smsempire.com/api/v1 之下。把密钥作为 bearer token 发送即可完成认证。仅限服务端使用:本 API 不发送任何 CORS 响应头,在浏览器中使用的密钥就是已经泄露的密钥。
# 1. Create a key in your dashboard, then:
export SE_KEY="se_live_XXXXXXXXXXXX_..."
# 2. What will this cost?
curl -s https://smsempire.com/api/v1/prices?service=tg \
-H "Authorization: Bearer $SE_KEY"
# 3. Buy a number. The Idempotency-Key is required.
curl -s https://smsempire.com/api/v1/activations \
-H "Authorization: Bearer $SE_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"service":"tg","max_price":"0.35"}'
# 4. Collect the code (or let the webhook push it to you).
curl -s https://smsempire.com/api/v1/activations/90210 \
-H "Authorization: Bearer $SE_KEY"约定
- 金额是十进制字符串("0.28000"),绝不是 JSON 数字。请用 decimal 类型解析,不要用 float。
- 时间戳采用 RFC3339 UTC 格式。
- 错误格式始终是 {"error": {"code", "message", "request_id"}}。请依据 code 分支处理;message 可能变化。
- 在任何工单中附上 request_id:它能直接定位到我们日志中的这次请求。
- 限流信息通过 X-RateLimit-* 响应头返回,429 会带上 Retry-After。
幂等性(下单必需)
每次购买都必须带 Idempotency-Key 请求头,建议用 UUID。重复使用同一个键会返回原始响应并带上 Idempotent-Replay: true,而不是再买一个号码。
它只为一种情况而存在:你的 POST 超时了,而你不知道它是否已经生效。没有这个键,重试会买两次,不重试则会丢掉已经付过钱的号码。有了它,直接重试即可。
同一个键配上不同的请求体会返回 422,而不是静默重放——这种组合是你这边的 bug,掩盖它只会给你错误的结果。
max_price
可选,但强烈建议使用。如果你不指定国家,我们会按质量顺序尝试多个、价格各不相同的国家;max_price 会对每一个候选逐一校验,因此绝不会向你收取超过你认可的金额。如果没有符合的,会返回 409 PRICE_ABOVE_MAX 并附上当前价格,且不产生任何扣费。
Webhook
在控制台注册一个 HTTPS 端点。验证码一到达,我们就会向它发送 POST。投递保证至少一次,并在约两个半小时内按退避策略重试。
POST /your/webhook
X-SmsEmpire-Event: activation.code_received
X-SmsEmpire-Delivery: evt_01j8z... <- deduplicate on this
X-SmsEmpire-Timestamp: 1765432991
X-SmsEmpire-Signature: v1=3a7f...
{
"id": "evt_01j8z...",
"type": "activation.code_received",
"created_at": "2026-08-14T12:03:11Z",
"data": {
"activation_id": 90210,
"service": "tg",
"country": "2",
"number": "+79991234567",
"code": "483920",
"status": "received",
"price": "0.28000"
}
}对你的端点的要求:HTTPS、443 端口,且解析到公网地址。我们不跟随重定向,并拒绝私有地址、回环地址和云元数据地址。
校验 Webhook
签名为 HMAC-SHA256(secret, "<timestamp>.<原始报文>") 的十六进制编码。按常见程度排列的四个易犯错误:
- 对重新序列化后的 JSON 签名,而不是原始字节。重新序列化会改变字节,签名永远对不上。
- 忽略时间戳。没有 ±300 秒的校验,被截获的一次投递可以被永久重放给你。
- 用 == 比较,而不是恒定时间比较。
- 没有按 X-SmsEmpire-Delivery 去重。我们保证至少一次,而不是恰好一次。
import hmac, hashlib, time
from flask import request, abort
SECRET = "whsec_..." # from your dashboard
TOLERANCE = 300 # seconds
def verify(request):
raw = request.get_data() # RAW bytes, before json parsing
ts = request.headers.get("X-SmsEmpire-Timestamp", "")
sig_header = request.headers.get("X-SmsEmpire-Signature", "")
if abs(time.time() - int(ts)) > TOLERANCE: # blocks replays
abort(400)
expected = hmac.new(SECRET.encode(),
f"{ts}.".encode() + raw,
hashlib.sha256).hexdigest()
# More than one v1= during a secret rotation; any match is valid.
sigs = [p.split("=", 1)[1] for p in sig_header.split(",") if p.startswith("v1=")]
if not any(hmac.compare_digest(expected, s) for s in sigs):
abort(400)const crypto = require("crypto");
// express.raw({type: "application/json"}) — you need the RAW body, not the parsed one.
function verify(req, secret) {
const ts = req.get("X-SmsEmpire-Timestamp") || "";
const header = req.get("X-SmsEmpire-Signature") || "";
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(Buffer.concat([Buffer.from(ts + "."), req.body]))
.digest("hex");
return header
.split(",")
.filter((p) => p.startsWith("v1="))
.some((p) =>
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.slice(3))),
);
}轮换密钥后,旧密钥仍有效 24 小时,且该窗口内每次投递都用两个密钥同时签名——因此你可以无中断地完成部署。
错误
| code | 应对方式 |
|---|---|
| INVALID_API_KEY | 密钥缺失、格式错误或不存在。不要重试。 |
| KEY_REVOKED / KEY_EXPIRED | 请创建新密钥。不要重试。 |
| INSUFFICIENT_SCOPE | 该密钥创建时没有写权限。 |
| API_ACCESS_DENIED | 本账户的 API 已关闭,或你尚未充值。 |
| API_DISABLED | 全平台暂时关闭了 API。请稍后重试。 |
| IDEMPOTENCY_KEY_REQUIRED | 每次购买都要发送 Idempotency-Key 请求头。 |
| IDEMPOTENCY_KEY_REUSED | 同一个键配了不同的请求体。请修正你的客户端。 |
| IDEMPOTENCY_IN_PROGRESS | 第一次尝试仍在处理中。请一秒后重试。 |
| PRICE_ABOVE_MAX | 价格已超过你的 max_price。请重新询价再决定。 |
| INSUFFICIENT_BALANCE | 请充值。充值前不要重试。 |
| COUNTRY_OUT_OF_STOCK | 当前没有号码。请重试或换一个国家。 |
| PROVIDER_CAPACITY | 该国家暂时无法供货。请稍后重试。 |
| CONCURRENCY_LIMIT | 待处理的激活过多。请先完成或取消一些。 |
| SPEND_LIMIT | 已达每小时消费上限。请等本窗口结束后重试。 |
| RATE_LIMITED | 请放慢速度,并遵守 Retry-After。 |
| VALIDATION_ERROR | 请求体有误。不要原样重试。 |
激活的生命周期
激活在收到验证码前处于 pending;收到后变为 received,你标记为已用则是 finished,也可以被取消。取消会原额退回你被扣除的金额。
待处理的激活在购买 20 分钟后过期。我们会向网络发起取消,并在该取消被确认后向你全额退款——你绝不会为一个什么都没收到的号码买单。
号码在购买后约 120 秒内不能取消;过早取消会返回 EARLY_CANCEL_DENIED,并附上还需等待的秒数。
限制
读请求、写请求和每用户总请求各有每分钟上限,另有可选的待处理激活数、每小时消费额和每小时激活数上限。你当前的数值可在 GET /api/v1/me 查看,其中 null 表示不限。需要调高请开工单:这些是按账户配置的,并非写死在代码里。
完整接口参考
交互式 OpenAPI 3.1 文档,始终由运行中的代码生成。
打开参考文档