
用Python调用短信接口发验证码,核心就是一次带签名的HTTP请求:把手机号、模板编号和验证码交给平台,平台审核通过的模板会替你把短信发出去。代码通常几十行就够,真正花时间的是前后那些细节。
我们帮不少客户联调过,后端用Python的团队一般上手很快,但上线后出问题的地方也很集中:密钥写死在代码里、请求没设超时、用户狂点按钮收到好几条、验证码校验完没作废。下面按实际开发顺序,把这些点一个个说透。
调用短信接口前要准备什么
写代码之前,账号这边要先准备好,不然接口调通了也发不出去。
- 签名和模板:在平台后台提交短信签名和验证码模板,审核通过后拿到模板编号。模板里的变量位置要和你后面传的参数对得上,比如“您的验证码是{code},{minutes}分钟内有效”。
- 接口凭证:一般是一个应用ID和一个密钥,密钥只给后端用,不能放在前端或者小程序代码里。
- 服务器出口IP:不少平台要求把调用方的服务器IP加进白名单,本地电脑调试时记得把自己的IP也加上,或者直接在服务器上测。
- 接口文档:重点看请求地址、签名算法、返回码说明这三块。
本文的字段名都是通用示意,不对应任何一家平台,实际以你签约平台的短信接口文档为准。
python发短信的核心代码怎么写
大部分平台的短信接口都是HTTP加签名的形式。用 requests 库发一个POST请求,把参数按规则排序、拼接、用密钥算出签名,一起提交过去。
import hashlib
import hmac
import json
import os
import time
import uuid
import requests
API_URL = os.environ["SMS_API_URL"]
APP_ID = os.environ["SMS_APP_ID"]
APP_SECRET = os.environ["SMS_APP_SECRET"]
def build_sign(params):
raw = "&".join(f"{k}={params[k]}" for k in sorted(params))
return hmac.new(APP_SECRET.encode(), raw.encode(), hashlib.sha256).hexdigest()
def send_code(phone, code):
params = {
"app_id": APP_ID,
"phone": phone,
"template_id": "TPL_LOGIN",
"template_params": json.dumps({"code": code, "minutes": "5"}),
"timestamp": str(int(time.time())),
"nonce": uuid.uuid4().hex,
}
params["sign"] = build_sign(params)
resp = requests.post(API_URL, data=params, timeout=5)
resp.raise_for_status()
return resp.json()
几个地方单独说一下。密钥从环境变量读,别写进代码仓库,很多泄露都是代码传到公共仓库导致的。timeout 一定要设,不设的话平台那边一卡,你的接口线程就跟着挂住。时间戳和随机串是防重放用的,签名算法以文档为准,有的用MD5,有的用HMAC,排序规则和是否做URL编码也各有不同。
验证码怎么生成、存储和限频
短信接口只负责发,验证码本身要你自己生成、自己保存、自己校验。生成别用 random,用 secrets,它是给安全场景准备的。存储一般放 Redis,设好过期时间,到点自动失效。
import secrets
import redis
r = redis.Redis()
def request_code(phone):
if r.exists(f"sms:lock:{phone}"):
return "发送太频繁,请稍后再试"
code = f"{secrets.randbelow(1000000):06d}"
r.set(f"sms:code:{phone}", code, ex=300)
r.set(f"sms:lock:{phone}", 1, ex=60)
result = send_code(phone, code)
return result
这里同一个手机号60秒内只能发1次,验证码5分钟有效。光靠这一道锁还不够,短信接口被刷往往不是单个号码的问题,同一个IP、同一台设备每天的发送次数也要设上限,登录页最好再加图形验证或滑块,不然被人拿脚本刷接口,短信费和通道信誉一起受损。
校验时容易漏掉的一步
用户提交验证码后,比对成功就要立刻把这条记录删掉,不能让同一个验证码反复使用。比对失败也要计数,同一手机号连续输错几次就让验证码作废,要求重新获取。
返回结果和异常怎么处理
短信接口的返回一般包含一个状态码和一个消息ID。状态码表示“请求被接收”,不等于短信已经到了用户手机,最终是否送达要看状态报告。
处理返回时,我们的习惯是分三种情况:

图 1:返回码先分清能不能重试,再决定记录、告警还是重发。
- 成功:把消息ID和手机号、发送时间一起记日志,后面查“收不到”的投诉全靠它;
- 业务错误:比如签名不对、模板未审核、余额不足、号码格式错,这类重试没用,直接记录并告警;
- 网络超时或平台5xx:可以重试,但最多1次,而且要注意超时不代表没发出去,盲目重试就会让用户收到两条验证码。
如果平台支持状态报告回调,可以用 Flask 或 FastAPI 写一个接收地址,按消息ID更新数据库里的发送状态,收到推送后按约定返回成功,避免平台重复推送。
用Python调用短信接口时常见的坑
把我们联调时碰到最多的问题列出来,大多跟代码能力无关:
- 签名一直报错:通常是参数排序、空值是否参与签名、编码方式没按文档来,把拼接前的字符串打印出来和文档示例逐字对比,最快;
- 本地能发,服务器发不出:多半是服务器IP没加白名单,或者服务器时间不准,时间戳校验没过;
- 模板变量传错:模板里写的是{code},代码里传成了 verify_code,平台会拒绝发送;
- 中文乱码:请求头和编码不一致,统一用UTF-8;
- 测试时用同一个号码反复发,触发了平台的频率限制,被误以为是接口坏了。
另外,验证码短信只发给正在操作的本人,内容别夹带营销信息。想顺手推活动的,应该走营销短信,单独取得用户同意,并带上退订方式。
结论
Python调用短信接口发验证码,流程可以概括成一条线:后台审好签名和模板,代码里安全保存密钥,按文档签名后发请求,验证码用 secrets 生成、Redis 存储并限频,校验后立刻作废,返回结果分类处理并记好日志。把这几步做扎实,python发短信基本不会给你添麻烦。如果你正在做登录注册或订单通知,需要稳定的短信群发通道和接口联调支持,可以拨打 18720919319 联系蔚岚科技,或在联系我们页面留言;网站或小程序还没上线的,也可以了解一下网站建设和小程序开发。
常见问题
Python有没有现成的短信接口库可以直接装?
有些平台会提供自己的Python SDK,装上就能用。没有SDK也不要紧,用 requests 按文档自己封装,代码量不大,出问题也更好排查。
验证码有效期设多久比较合适?
一般设5分钟左右。太短用户来不及输入,太长被截获后的风险变大,有效期要和短信模板里写的分钟数保持一致。
发送接口要不要做成异步?
用户量小的时候同步调用就行。并发上来后,可以放进 Celery 之类的任务队列异步发送,但要给前端一个“已发送”的及时反馈。
测试阶段怎么避免浪费短信条数?
先用平台提供的测试账号或沙箱环境调通签名,再用1到2个自己的手机号小批量测试。开发环境可以加个开关,只打印验证码不真发。
短信接口返回成功,用户却说没收到怎么办?
先拿消息ID查状态报告,看是否送达。没送达再看号码状态、手机拦截设置和通道情况,把日志发给平台技术一起排查。