Webhook 通知
使用 Webhook 在您的应用中接收二维码交付和续费结果。
简介
使用 Webhook 在您的应用中接收二维码交付和续费结果。
什么是 Webhook?
iRoamly 会针对二维码交付和续费结果发送 Webhook 通知。每个 Webhook 均使用 HTTPS,并包含 JSON 负载。您可以使用 Webhook:
- 在兑换码转换为二维码或直接购买二维码完成后接收二维码信息。
- 通过 Webhook 接收续费结果:续费二维码就绪时发送
esim.renewal.success,上游续费订单失败时发送esim.renewal.failed。
如果您的端点未返回 2xx 状态,iRoamly 最多共尝试推送 5 次:第一次立即推送,之后分别在 30 秒、2 分钟、3 分钟和 5 分钟后重试。
接收 Webhook 的步骤
按照以下步骤,即可开始在您的应用中接收实时事件:
- 创建用于接收请求的本地端点
- 在 iRoamly Shop 管理后台的“开发者 > Webhook 通知”中注册您的 Webhook 端点
1. 创建用于接收请求的本地端点
在您的本地应用中,创建一个可接受 POST 请求的新路由。
例如,您可以在 Next.js 中添加一个 API 路由:
import type { NextApiRequest, NextApiResponse } from 'next';
export default (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
const payload = req.body;
console.log(payload);
res.status(200);
}
};收到事件后,应返回 HTTP 200 OK,以告知 iRoamly 事件已成功送达。
2. 注册您的开发用 Webhook 端点
在 iRoamly Shop 管理后台中注册您可公开访问的 HTTPS URL。
您可以使用 ngrok 等工具创建通往本地服务器的隧道。

3. 测试您的 Webhook 端点是否正常工作
将一个测试兑换码转换为二维码,或直接购买一个二维码,检查您的 Webhook 端点是否收到事件。
事件类型
支持的事件类型及其负载列表。
推送
所有事件的推送方式相同:向订阅该事件的每个端点发送带 JSON 请求体的 POST 请求。任何 2xx 响应均视为已送达;否则会重试,最多共尝试 5 次(第一次立即推送,之后分别在 30 秒、2 分钟、3 分钟和 5 分钟后重试)。每个请求都携带以下请求头:
签名校验方法请参阅“验证 Webhook 请求”。
esim.qrcode
兑换或购买 eSIM 二维码后返回的 eSIM 二维码相关信息。
请求体示例
{
"code": 0,
"data": {
"activation_code": "CD112885573C49DDA42F64C905B925F4",
"code": "UtTpoKCe769u2tt",
"create_at": "2025-09-01 11:34:30",
"isrenew": 0,
"order_id": "ESIM202509021134343282",
"plan_name": "Japan-eSIM-3GB-15 Days-Daily",
"qrcode": "https://www.iroamly.com/qrcode/20250901/xSQfryvOWo0QUhsGcbpeTm9wWYfGu9BQ.png",
"qrcode_content": "LPA:1$rsp1.cmlink.com$CD352885573C49DDA42F64C905B925F4",
"sm_dp_address": "rsp1.cmlink.com"
},
"event_type": "esim.qrcode"
}esim.renewal.success
续费成功时发送:上游供应商返回续费订单的下载信息后,系统会为续费兑换码生成二维码,其 status 变为 2(已兑换)。
请求头
esim.renewal.success与所有事件一样,该请求还携带 X-Webhook-Delivery、X-Webhook-Timestamp、X-Webhook-Signature 和 X-Webhook-Signature-V2(见“事件类型”)。
请求体示例
{
"code": 0,
"data": {
"activation_code": "CD112885573C49DDA42F64C905B925F4",
"code": "RnB7xTq2Lm9pQaZ",
"create_at": "2026-10-28 09:12:41",
"download_url": "LPA:1$rsp1.cmlink.com$CD352885573C49DDA42F64C905B925F4",
"iccid": "8944501234567890123",
"iscouldrenewal": 1,
"isrenew": 1,
"plan_name": "Japan-eSIM-3GB-15 Days-Daily",
"qrcode": "https://www.iroamly.com/qrcode/20261028/xSQfryvOWo0QUhsGcbpeTm9wWYfGu9BQ.png",
"qrcode_content": "LPA:1$rsp1.cmlink.com$CD352885573C49DDA42F64C905B925F4",
"renew_order_id": "TG202610280912410001",
"renewal_father_code": "UtTpoKCe769u2tt",
"sm_dp_address": "rsp1.cmlink.com"
},
"event_type": "esim.renewal.success"
}字段
esim.renewal.failed
续费失败时发送:上游供应商对续费订单返回了 0000 以外的代码,或向供应商发出的请求失败。
(注:上游供应商回调缺失或延迟不会触发此事件。请设置超时并回退为查询兑换码——见“处理续费事件”。)
请求头
esim.renewal.failed与所有事件一样,该请求还携带 X-Webhook-Delivery、X-Webhook-Timestamp、X-Webhook-Signature 和 X-Webhook-Signature-V2(见“事件类型”)。
请求体示例
{
"code": -1,
"data": {
"code": "RnB7xTq2Lm9pQaZ",
"create_at": "2026-10-28 09:12:41",
"error_msg": "TuGe_RenewOrderEx RespCode: 9999",
"isrenew": 1,
"plan_name": "Japan-eSIM-3GB-15 Days-Daily",
"renewal_father_code": "UtTpoKCe769u2tt"
},
"event_type": "esim.renewal.failed"
}字段
续费与 esim.qrcode
续费不再以 esim.qrcode 推送;该事件用于二维码转换和直接购买二维码。与 esim.qrcode 负载相比:
event_type和X-Webhook-Event请求头为esim.renewal.success或esim.renewal.failed,且isrenew为1。data.code是续费生成的新兑换码;被续费的兑换码为data.renewal_father_code。esim.renewal.success新增了renew_order_id、renewal_father_code、iscouldrenewal、download_url和iccid,且不含order_id。- 续费失败通过
esim.renewal.failed报告,顶层code为-1,上游错误信息位于data.error_msg。
事件订阅
新端点默认订阅全部三种事件。现有端点会自动接收 esim.renewal.success 和 esim.renewal.failed——无需任何更改。
处理续费事件
- 确保处理逻辑幂等。重试可能导致同一事件被多次推送,因此请按
X-Webhook-Delivery或event_type+data.code去重,并忽略已处理过的事件。 - 使用超时回退。如果在合理时间内既未收到
esim.renewal.success也未收到esim.renewal.failed,请使用查询 API(例如“查询二维码”)查询兑换码,而不要直接认定续费失败。 - 不要根据
esim.renewal.failed决定退款。该事件仅表示上游订单失败;是否以及何时退还点数另行决定,因此请勿根据此事件向您的客户退款。
验证 Webhook 请求
验证 Webhook 请求上的 iRoamly 签名。
如何验证 iRoamly 签名?
iRoamly 签名通过 Webhook 请求的 X-Webhook-Signature 请求头发送,格式为 sha256= 后接十六进制编码的签名。签名使用 HMAC-SHA256 算法生成,以 Webhook 密钥为密钥,以原始请求体(与收到时完全一致,未经任何 JSON 解析)为消息。
Webhook 请求头示例
{
'X-Webhook-Signature': 'sha256=dd7bdd2cf1f6bac6e171c6c508c157b7cd3cc1fd196394277fb59ba0bdd9b87b'
}您可以在管理后台的“开发者 > Webhook 通知”中找到 Webhook 密钥。

可使用以下代码片段生成签名:
import * as crypto from 'crypto';
generateSignature(payload: string, secret: string): string {
const computedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return 'sha256=' + computedSignature;
}在上面的代码片段中,payload 是原始请求体,secret 是 Webhook 密钥。只需将生成的签名(包括 sha256= 前缀)与 X-Webhook-Signature 请求头的值进行比较,即可完成验证。
带时间戳的签名(X-Webhook-Signature-V2)
每个请求还携带 X-Webhook-Signature-V2:v1= 后接使用同一 Webhook 密钥对 {timestamp}.{body}(即 X-Webhook-Timestamp 请求头的值、一个点号和原始请求体)计算的十六进制编码 HMAC-SHA256 值。
轮换 Webhook 密钥后,旧密钥在 24 小时内仍然有效。在此期间,X-Webhook-Signature 仍使用旧密钥签名,而 X-Webhook-Signature-V2 为每个密钥各携带一个值,以逗号分隔(v1=<old>,v1=<new>):只要任一值匹配,即可接受该请求。