规则
短信类型
目前 SendCloud 的短信服务支持验证码, 行业通知和营销短信.
验证码短信:注册密码, 修改密码, 身份有验证等.
行业通知短信:由用户行为触发生产的通知, 如:订单通知, 回复通知等.
营销短信: 向用户统一发送的消息通知等.
接入方式
目前 SendCloud 对短信业务提供 WEBAPI 的接入方式, 开发者可以利用 SendCloud 提供的 HTTP 接口, 调用 SendCloud 的服务.
使用我们的短信API, 开发者需要创建:
- SMS_USER
- SMS_KEY
- 短信模板
- 签名
SMS_USER 与 SMS_KEY
SMS_USER是调用接口发信时候的账号
用户可以通过【短信语音】-【发送设置】-【发送授权】
来创建 SMS_USER, 同时 SendCloud 会自动生成对应的 SMS_KEY. 目前 SendCloud 只允许拥有一个 SMS_USER.
短信模板
短信模板, 用户在发送短信之前,必须在前台页面编辑短信内容, 并提交审核. 审核通过后, 通过模板的 ID 调用发送.
变量替换
SendCloud 支持在短信中使用「变量」.
变量的格式: 首尾使用 %
包围, 即为用户定义的一个变量. 举例:
欢迎使用爱发信. 您的手机验证码是: %code%. # code是变量
变量的用法:
- 在短信模板中使用变量, 作为占位符
- 在短信 API 中设置变量的值
- SendCloud 会根据请求参数, 来替换短信内容中相应变量的值
- 变量的值的长度不能超过 16或32 个字符, 变量的值中不能含有 HTTP 链接
变量的命名规则:
- 变量名可包含字母(大小写均可)、数字、'_'(下划线)或'-'(中划线)的任意组合,不得出现其他字符
- 变量名以字母(大小写均可)、数字、'_'(下划线)或'-'(中划线)开头
- 变量名长度不得超过16或32个字符
签名
由于短信发送的特殊性, 以及相关的 ISP 机构的审查制度, 短信内容中必须含有能说明发送者身份的签名. 目前, SendCloud 支持在编辑短信模板时添加签名, 并设置签名位置(目前只支持签名在短信尾部).
注意:
短信模板必须含有「签名」, 否则不能通过审核.
短信备案
短信备案是指在短信运营商处进行信息审核,未备案可能影响用户的发信成功率。
用户第一次调用短信接口请求成功后,可在【短信语音】-【发送设置】-【短信备案】中进行短信备案,步骤如下:
第一步,点击下载备案承诺书,打印成为纸质版;
第二步,仔细阅读承诺书,负责人进行签字,并加盖公司公章;
第三步,通过拍照或者扫描的方式,转换为电子版图片,JPG或PDF格式;
第四步,点击上传备案承诺书,上传图片,上传成功后等待审核,SendCloud将在1个工作日内出审核结果。
拦截列表
为减少用户对空号, 停机等手机号码的无效发送, SendCloud 会根据运营商反馈的发送结果来做相应的拦截处理.
所有发送失败
的手机号码都会进入拦截列表, 拦截列表记录的字段如下:
- 手机号码: 拦截的手机号码
- 拦截时间: 此时间之后的发送会被 SendCloud 拦截
- 失效时间: 此时间之后的发送恢复正常
- 原因: 运营商反馈的发送结果明细
不同的失败原因会产生不同的拦截时间, 不同的拦截范围, 详细见下表:
返回码 | 失败描述 | 拦截时间 | 全局,局部 | 用户是否可删除 |
---|---|---|---|---|
500 | 发送失败, 手机空号 | 30 天 | 全局拦截 | 是 |
510 | 发送失败, 手机停机 | 1 小时 | 全局拦截 | 是 |
520 | 发送失败,手机号码在黑名单 | 1 小时 | 局部拦截 | 是 |
530 | 发送失败, 对方占线 | 0 秒 | 是 | |
540 | 发送失败, 无人接听 | 0 秒 | 是 | |
550 | 发送失败, 该模板内容被拦截 | 1 小时 | 局部拦截 | 是 |
560 | 发送失败, 手机终端问题 | 1 小时 | 全局拦截 | 是 |
570 | 发送失败, 手机不在服务区 | 1 小时 | 全局拦截 | 是 |
580 | 发送失败, 手机关机 | 0 秒 | 是 | |
590 | 发送失败, 其他原因 | 0 秒 | 是 |
全局拦截: 此条拦截记录会对 SendCloud 所有用户生效.
局部拦截: 此条拦截记录只会对来源用户生效.
用户可以在拦截列表中删除来源是自己
的记录, 对于来源不是自己
的拦截手机号, 可以在确认手机号通讯正常之后, 联系客服处理.
API 验证机制
短信发送使用数字签名的验证模式. 这种模式能够有效避免密码在传输途中的泄露, 是安全级别很高的一种加密验证方式.
数字签名的验证模式: 调用 API 时, 用户不需要把密码 ( SMS_KEY ) 作为参数明文传输, 而是将数字签名 ( signature ) 作为参数传输给服务器. 服务器端会验证此 signature 的正确性.
短信API验证机制中,提供MD5、SHA256两种验证方法
生成数字签名 ( signature ) 的方法:
1. 将实际调用API的参数以字母升序(A-Z)排列, 不包括 smsKey 和 signature 字段本身
2. 按照排列之后的顺序, 以 'key=value' + '&' + 'key=value' 的方式连接所有参数,
得到字符串 param_str
3. 以 SMS_KEY + '&' + param_str + '&' + SMS_KEY 的方式得到字符串 sign_str
4. 计算 sign_str 的MD5值 (32位, 不区分大小写)或sha256, 得到 signature
提示:
1. 生成签名时, 参数不要使用 'urlencode'. 在调用 api 时, 才需要对参数做 'urlencode'
2. '&' 是代码中使用的连接符, '+'是文档显示之用
下面提供了一个代码示例 ( python ):
import hashlib
SMS_USER = 'testuser'
SMS_KEY = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
param = {
'smsUser': SMS_USER,
'templateId' : 1,
'phone' : 18888888888,
'vars' : {},
}
param_keys = list(param.keys())
param_keys.sort()
param_str = ''
for key in param_keys:
param_str += key + '=' + str(param[key]) + '&'
param_str = param_str[:-1]
sign_str = SMS_KEY + '&' + param_str + '&' + SMS_KEY
signature = hashlib.sha256(sign_str).hexdigest()
提示: 所有的 API 都支持 HTTPS.
timestamp 时间戳 ( 提升逼格 )
用户可以在每个 API 请求中加入 timestamp 参数, SendCloud 会检查 timestamp 和 服务器当前时间, 如果两者相差大于60秒, 则请求会被拒绝.
用户需要通过调用 API 来获取 SendCloud 服务器的时间戳, 而不是自己的本地时间.
timestamp 参数需要被包含在 signature 中, 参与生成数字签名.
SMSHook
SMSHook 机制
用户将短信、语音请求发送给 SendCloud 之后, SendCloud 会把「请求结果」同步返回给用户, 而短信、语音的「发送结果」和 「其他事件结果」是通过 SMSHook 异步返回给用户的.
- SendCloud 为客户提供了一些事件, 客户可以选择关注某些事件
- 当某事件发生, 就会触发 SendCloud 向客户设置的 URL 发送数据 ( POST )
- SendCloud推送的数据类型:"Content-Type", "application/x-www-form-urlencoded"
- 客户收到数据, 解析出事件和数据, 做后续的处理
目前 SendCloud 支持的事件如下:
事件 | 触发条件 |
---|---|
请求(request) | 请求成功 |
送达(deliver) | 发送成功 |
处理失败(workererror) | 处理失败 |
发送失败(delivererror) | 发送失败 |
点击(click) | 用户点击链接 |
回复(reply) | 用户回复(仅短信有) |
短信上行(sms_mo) | 用户主动提交短信给接口 |
模板审核(templateVerify) | 短信模板的审核结果消息 |
使用方法:
- 用户自行编写 HTTP 服务, 使之能够处理相应的事件, 解析相关数据, 并开放出相应URL
- 用户在 SendCloud 的
【短信语音】- 【发送设置】-【SMSHook】
中选择关注的事件, 配置接收数据的 URL
注意: 我们会对用户提供的 URL 做检测. 需要此 HTTP 服务能够正确响应 get | post 请求, 并且保证返回的 HTTP 状态码 为 200
数字签名验证
为了确保消息的来源身份是 SendCloud, 你可以选择对 POST 数据的来源进行安全认证. ( 不验证, 直接解析 POST 的数据也可以 ).
安全认证的方法如下:
- 通过
【短信语音】- 【发送设置】-【SMSHook】
获取APP KEY
- 解析出 POST 数据中的
token
,timestamp
和signature
- 使用
APP KEY
,token
和timestamp
生成数字签名signature
, 与 POST 数据中的signature
进行校验 ( 签名算法: SHA256)
python 代码示例
import hashlib, hmac
def verify(appkey, token, timestamp, signature):
return signature == hmac.new(
key=appkey,
msg='{}{}'.format(timestamp, token),
digestmod=hashlib.sha256).hexdigest()
Java 代码示例 (依赖 apache codec)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
public boolean verify(String appkey, String token, long timestamp,
String signature) throws NoSuchAlgorithmException, InvalidKeyException {
Mac sha256HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(appkey.getBytes(),"HmacSHA256");
sha256HMAC.init(secretKey);
StringBuffer buf = new StringBuffer();
buf.append(timestamp).append(token);
String signatureCal = new String(Hex.encodeHex(sha256HMAC.doFinal(buf
.toString().getBytes())));
return signatureCal.equals(signature);
}
php 代码示例
function verify($appkey,$token,$timestamp,$signature){
$hash="sha256";
$result=hash_hmac($hash,$timestamp.$token,$appkey);
return strcmp($result,$signature)==0?1:0;
}
重试机制
如果遇到 URL 访问错误或超时, SendCloud 最多会重试 6 次. 每次重试的时间间隔最快为 3min, 10min, 30min, 1h, 6h, 12h, 24h. 即在消息丢失前, 你有足够的时间来修复 URL.
如果超过重试次数,SendCloud 将会把消息丢弃.
每次事件处理, 数据解析, 你需要在 3s 内返回状态码 200, 否则, SendCloud 将会重发该条消息。
事件说明
目前 SMSHook 支持的事件类型包括: 请求, 送达, 处理失败, 发送失败, 用户回复.
请求 ( request )
参数说明
参数 | 类型 | 说明 |
---|---|---|
event | string | 事件类型:"request" |
eventType | int | 事件类型代码:1 |
message | string | 消息内容 |
smsUser | string | smsUser |
smsIds | list | 短信(或语音)ID组成的数组 |
templateId | int | 模板ID |
phones | list | 手机号组成的数组 |
timestamp | long | 时间戳 |
token | string | 随机产生的长度为50的字符串 |
signature | string | 数字签名字符串 |
userId | int | 用户ID |
labelId | int | 预留, 暂不用 |
tag | string | 用户自定义标签 |
msgCount | int | 条数 |
msgType | int | "0"短信, "1"彩信, "2"国际短信, "3"国内语音 |
smsType | int | "0"验证码, "1"通知, "2"营销 |
POST 数据示例
{
"msgType":0,
"signature":"1ff237043487aeb4dc1b21c22b5ead9e4df94a31a3afa0ad53238eb38c2cbeea",
"phones":"[\"13888888888\"]",
"eventType":1,
"templateId":29999,
"message":"request",
"userId":19999,
"smsUser":"App",
"smsIds":"[\"1652150994014_9373_14466_36735_99drnc$13888888888\"]",
"token":"VDymF6ihuJkKZjHiJZkLKGmY6q9qAQ2WGopLh7mBsDeAO6GKV5",
"labelId":0,
"smsType":0,
"tag":0,
"event":"request",
"timestamp":1652150994087
}
送达 ( deliver )
参数说明
参数 | 类型 | 说明 |
---|---|---|
event | string | 事件类型:"deliver" |
eventType | int | 事件类型代码:2 |
message | string | 消息内容 |
smsUser | string | smsUser |
smsId | string | 短信(或语音)ID |
templateId | int | 模板ID |
phone | string | 手机号 |
timestamp | long | 时间戳 |
token | string | 随机产生的长度为50的字符串 |
signature | string | 数字签名字符串 |
userId | int | 用户ID |
labelId | int | 预留, 暂不用 |
tag | string | 用户自定义tag |
msgCount | int | 条数 |
msgType | int | "0"短信, "1"彩信, "2"国际短信, "3"国内语音 |
smsType | int | "0"验证码, "1"通知, "2"营销 |
outboundTime | string | 通道时间 |
receiptTime | string | 回执时间 |
POST 数据示例
{
"outboundTime":"2022-05-10 01:29:31",
"msgType":0,
"signature":"9ca96fa072bfa048969aa0cb7bf7baf64100234640a1b9793cca1a419afb9cb8",
"eventType":2,
"templateId":29999,
"message":"Successfully delivered",
"userId":19999,
"smsUser":"APP",
"token":"4mRG9lGhVb3jZhOMnksFPBtX1OLDMNZMfXTFHkFd9eybfdRiHM",
"smsId":"1652117371408_19999_376_4631_qrwnpq$13888888888",
"receiptTime":"2022-05-10 01:29:50",
"labelId":0,
"phone":"13888888888",
"msgCount":1,
"smsType":1,
"tag":0,
"event":"deliver",
"timestamp":1652117390000
}
处理失败 ( workererror)
参数说明
参数 | 类型 | 说明 |
---|---|---|
event | string | 事件类型:"workererror" |
eventType | int | 事件类型代码:4 |
message | string | 消息内容 |
encodeMessage | string | base64编码的消息内容 |
statusCode | int | 错误码 |
smsUser | string | smsUser |
smsId | string | 短信(或语音)ID |
templateId | int | 模板ID |
phone | string | 手机号 |
timestamp | long | 时间戳 |
token | string | 随机产生的长度为50的字符串 |
signature | string | 数字签名字符串 |
userId | int | 用户ID |
labelId | int | 预留, 暂不用 |
tag | string | 用户自定义tag |
msgCount | int | 条数 |
msgType | int | "0"短信, "1"彩信, "2"国际短信, "3"国内语音 |
smsType | int | "0"验证码, "1"通知, "2"营销 |
outboundTime | string | 通道时间 |
POST 数据示例
{
"outboundTime":"2022-05-10 00:00:54",
"msgType":0,
"signature":"8a3a030169decf0134010b8d7443cfbbf16b08a6aee524291706dacfb127be95",
"eventType":4,
"templateId":-3,
"message":"smsworker:address in unsubscribe list(取消订阅)",
"userId":19999,
"smsUser":"APP",
"token":"sGfR3yMheseXBxkPt3NnIuDQK5aYdzbfyUO8i0oz6IJI07pNHj",
"smsId":"1652112054796_19999_167_-3_ty8pqn$13888888888",
"labelId":0,
"encodeMessage":"c21zd29ya2VyOmFkZHJlc3MgaW4gdW5zdWJzY3JpYmUgbGlzdCjlj5bmtojorqLpmIUp",
"phone":"13888888888",
"msgCount":1,
"smsType":1,
"tag":0,
"event":"workererror",
"timestamp":1652112054846,
"statusCode":430
}
发送失败 ( delivererror)
参数说明
参数 | 类型 | 说明 |
---|---|---|
event | string | 事件类型:"delivererror" |
eventType | int | 事件类型代码:5 |
message | string | 消息内容 |
encodeMessage | string | base64编码的消息内容 |
statusCode | int | 错误码 |
smsUser | string | smsUser |
smsId | string | 短信(或语音)ID |
templateId | int | 模板ID |
phone | string | 手机号 |
timestamp | long | 时间戳 |
token | string | 随机产生的长度为50的字符串 |
signature | string | 数字签名字符串 |
userId | int | 用户ID |
labelId | int | 预留, 暂不用 |
tag | string | 用户自定义tag |
msgCount | int | 条数 |
msgType | int | "0"短信, "1"彩信, "2"国际短信, "3"国内语音 |
smsType | int | "0"验证码, "1"通知, "2"营销 |
outboundTime | string | 通道时间 |
receiptTime | string | 回执时间 |
POST 数据示例
{
"outboundTime":"2022-05-10 09:31:12",
"msgType":0,
"signature":"785370449703a5dfca4cb773a5a266f6be1db8bdbe713b8ce74ca572f9788a7a",
"eventType":5,
"templateId":29999,
"message":"REJECTD(其他)",
"userId":19999,
"smsUser":"APP",
"token":"MTha34FTrBRBmJXdZK4qVCqRxh8N4IlAJlM11sd1FSfCk9jmo3",
"smsId":"1652146271665_19999_8755_3883_37059m$13888888888",
"receiptTime":"2022-05-10 09:31:17",
"labelId":0,
"encodeMessage":"UkVKRUNURCjlhbbku5Yp",
"msgCount":1,
"smsType":0,
"tag":0,
"event":"delivererror",
"timestamp":1652146277000,
"statusCode":590
}
点击 (click)
参数说明
参数 | 类型 | 说明 |
---|---|---|
event | string | 事件类型:"click" |
eventType | int | 事件类型代码:10 |
message | string | 消息内容:click sms |
smsUser | string | smsUser |
smsId | string | 短信(或语音)ID |
templateId | int | 模板ID |
clickUrl | string | 点击链接 |
phone | string | 手机号 |
timestamp | long | 时间戳 |
token | string | 随机产生的长度为50的字符串 |
signature | string | 数字签名字符串 |
userId | int | 用户ID |
labelId | int | 预留, 暂不用 |
tag | string | 用户自定义tag |
msgType | int | "0"短信, "1"彩信, "2"国际短信, "3"国内语音 |
smsType | int | "0"验证码, "1"通知, "2"营销 |
ip | string | ip |
deviceName | tring | 设备名称 |
deviceType | int | 设备类型(1:PC,2:手机,3:平板,0:其他) |
oSName | string | 操作系统名称 |
oSVer | string | 操作系统版本 |
explorerName | string | 浏览器名称 |
explorerVer | string | 浏览器版本 |
POST 数据示例
{
"clickUrl": "https://ifaxin.com",
"deviceType": "1",
"msgType": "0",
"signature": "ee7a01678cdc69a53074a0f5516c93541c7d0bd60758d5344ece26b8347002e8",
"ip": "124.127.61.82",
"eventType": "10",
"templateId": "868058",
"message": "click sms",
"userId": "15",
"smsUser": "sms_ss",
"deviceName": "Other",
"token": "uTuoR0IzT1OSQqP1ykjG5QP1cQF9rdomhLkGkse3FbZwhE7UF7",
"oSName": "Windows 7",
"explorerName": "Chrome",
"smsId": "1668413622360_15_9_868058_uny9w1$13437150000",
"explorerVer": "86.0.4240",
"labelId": "0",
"tag": "0",
"phone": "13437150000",
"smsType": "1",
"event": "click",
"oSVer": "",
"timestamp": "1668413648109"
}
回复 (reply)
参数 | 类型 | 说明 |
---|---|---|
event | string | 事件类型:"reply" |
eventType | int | 事件类型代码:6 |
smsUser | string | smsUser |
templateId | int | 模板ID |
phone | string | 手机号 |
timestamp | long | 时间戳 |
token | string | 随机产生的长度为50的字符串 |
signature | string | 数字签名字符串 |
userId | int | 用户ID |
labelId | int | 预留, 暂不用 |
replyContent | string | 回复内容 |
encodeReplyContent | string | base64编码的回复内容 |
replyTime | Datetime | 回复时间 |
tag | string | 用户自定义tag |
msgCount | int | 条数 |
msgType | int | "0"短信, "1"彩信, "2"国际短信, "3"国内语音 |
smsType | int | "0"验证码, "1"通知, "2"营销 |
POST 数据示例
{
"msgType":0,
"signature":"3a9ab5dd4ca5ee56dee8c8998f11a1ec33ce5bcd720987aff86b6608fc967a3d",
"eventType":6,
"templateId":29999,
"userId":19999,
"smsUser":"APP",
"encodeReplyContent":"5a6i5pyN55S16K+d5piv5ZOq5Liq5Y+356CB",
"token":"MTY8WxQIUUWyKjke1MTWpPu88mxxmHINTsT7x2DwJZQq2VynNl",
"replyTime":"2022-05-10 08:49:14",
"labelId":0,
"phone":"13888888888",
"smsType":0,
"replyContent":"客服电话是哪个号码",
"tag":0,
"event":"reply",
"timestamp":1652143756604
}
短信上行 (sms_mo)
参数 | 类型 | 说明 |
---|---|---|
event | string | 事件类型:"sms_mo" |
eventType | int | 事件类型代码:7 |
smsUser | string | smsUser |
templateId | int | 模板ID |
phone | string | 手机号 |
timestamp | long | 时间戳 |
token | string | 随机产生的长度为50的字符串 |
signature | string | 数字签名字符串 |
userId | int | 用户ID |
labelId | int | 预留, 暂不用 |
replyContent | string | 上行回复内容 |
encodeReplyContent | string | base64编码的回复内容 |
replyTime | Datetime | 上行回复时间 |
tag | string | 用户自定义tag |
msgCount | int | 条数 |
msgType | int | "0"短信, "1"彩信, "2"国际短信, "3"国内语音 |
smsType | int | "0"验证码, "1"通知, "2"营销 |
POST 数据示例
{
"msgType":0,
"token":"0IwiTzCRJFwlS40cwQRPXP0j61xg3B9RNLFu5WpC9jD0CruWdT",
"timestamp":"1566293197107",
"labelId": 0,
"phone":"13888888888",
"replyContent":"test_mo",
"encodeReplyContent":"dGVzdF9tbw==",
"replyTime":"2019-08-16 16:16:16",
"userId":19999,
"templateId":-1,
"smsUser":"smsuser",
"event":"sms_mo",
"signature":"da7339b7ddbd1228c2bcd8abdc80e28ad37fba3dd5c73dfb18e6ca91e6ae7715",
"eventType": 7,
"tag":0,
"smsType":0
}
模板审核 (templateVerify)
参数 | 类型 | 说明 |
---|---|---|
event | string | 事件类型:"templateVerify" |
eventType | int | 事件类型代码:8 |
verfiyResult | int | 审核结果:0审核中,1通过,-1不通过 |
verfiyComment | string | 审核意见.审核不通过时有verfiyComment |
templateId | int | 模板ID |
name | string | 模板名称 |
msgType | int | 模板类型:0短信,1彩信,2国际短信,3语音 |
smsType | int | 短信类型:0验证码,1行业通知,2营销 |
timestamp | long | 时间戳 |
token | string | 随机产生的长度为50的字符串 |
signature | string | 数字签名字符串 |
userId | int | 用户ID |
POST 数据示例
{
"msgType":0,
"signature":"b80266d81f527c1ce870ba36c4c9e79fd9725c5a7c943577a25d8116aa67c927",
"verfiyResult":1,
"name":"感谢莅临上海车展展台",
"smsType":1,
"eventType":8,
"templateId":6255,
"event":"templateVerify",
"userId":102,
"token":"M1dgqmmOsM3BC9dCqBsMjtDh6I5jYwXngPEtcVV9v8XoplF1VQ",
"timestamp":1646628597226
}