要在本地主机上测试 Mailgun 的网点, 请向本地处理器曝光 。 npx portpreview PORT,为所需的 Mailgun 事件类型配置 HTTPS 端点,并在接受事件前验证有效载荷的时间戳、符号和 HMAC-SHA256 签名。
Mailgun 网页用户报告
Mailgun 当发生被配置的事件时,会发出带有JSON有效载荷的HTTP或HTTPPPOST. 当前的事件类型包括: accepted, (中文(简体) ). delivered, (中文(简体) ). temporary_fail, (中文(简体) ). permanent_fail, (中文(简体) ). opened, (中文(简体) ). clicked,垃圾邮件投诉,和不订阅。 依赖跟踪的事件只有在启用相应的跟踪时才会出现.
当前的 Mailgun Send webhook 机构有一个 signature 对象并列 event-data。事件数据包含以下字段: event, (中文(简体) ). id, (中文(简体) ). timestamp,消息信头,收件人信息,标签,以及发送细节,视事件类型而定. 禁止文件字段的守则,并容忍没有可选属性。 Mailgun官方网站 有效载荷实例 是合同测试的最佳装置。
请不要将 Mailgun Send 的 Webhook 与 Mailgun 混淆 警报 警告使用不同的签名密钥, 并签名整个 POST 机构为 X-Sign 头曰. 本指南涵盖发送webhooks:有效载荷中的签名字段和账户的Webhook Signing Key.
1. 建立本地Mailgun终点
与在生JSON正文上签名的方案不同,Mailgun Send所记录的计算使用了签名对象的时间戳和符号. 因此,标准JSON分析是适当的。 以下 Express 处理器验证 HMAC, 进行重放年龄检查, 并持久接受事件 。
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.use(express.json({ limit: '1mb' }));
function verifyMailgunSignature({ timestamp, token, signature }) {
if (!timestamp || !token || !signature) return false;
const expected = crypto
.createHmac('sha256', process.env.MAILGUN_WEBHOOK_SIGNING_KEY)
.update(String(timestamp) + String(token))
.digest('hex');
const expectedBytes = Buffer.from(expected, 'hex');
const actualBytes = Buffer.from(String(signature), 'hex');
return expectedBytes.length === actualBytes.length &&
crypto.timingSafeEqual(expectedBytes, actualBytes);
}
app.post('/webhooks/mailgun', async (req, res) => {
const signing = req.body?.signature;
const event = req.body?.['event-data'];
if (!signing || !event || !verifyMailgunSignature(signing)) {
return res.status(406).send('invalid webhook');
}
const ageSeconds = Math.abs(Date.now() / 1000 - Number(signing.timestamp));
if (!Number.isFinite(ageSeconds) || ageSeconds > 15 * 60) {
return res.status(406).send('stale webhook');
}
await acceptOnce({
eventId: event.id,
replayToken: signing.token,
payload: event,
});
return res.sendStatus(200);
});
app.listen(3000);
15分钟窗口是一个应用政策,而不是Mailgun授权值. Mailgun建议检查时间戳与当前时间不相去甚远,但警告不要因为交货可能被延迟而过于激进. 选择一个符合您队列和事件恢复要求的窗口,监视合法的拒绝,并刻意调整.
将 Webhook Signing Key 存储在一个秘密管理器或环境变量中, 绝不在源控中 。 Mailgun's 保护网络用户指南 定义精确的计算:没有分隔符的连接时间戳和符号,使用 Webhook Signing Key 计算 HMAC-SHA256,并将十六进制摘要与 signature。 。 。 。
2. HTTPS上空的本地主机
应用程序在端口3000上监听,运行:
npx portpreview 3000
将本地路线附加到公共HTTPS来源. 例如:
https://example.portpreview.dev/webhooks/mailgun
在测试期间让应用程序和隧道同时运行。 Mailgun 需要一个可公开获取的 URL; localhost,私有的局域网地址和自签的开发证书都不适合远程目的地。PortPreview终止了公共的HTTPS,并将请求转发到您的本地端口。
3. 配置 Mailgun 事件 URL
Mailgun支持账户级和域级的webhook配置. 账户级端点可以接收跨域的事件并继承子账户;域级端点仅适用于该域. 每个事件类型都是单独配置的,最多可有3个URL. 选择与应用程序相匹配的最窄范围。
- 为想要的账户打开Webhooks区域或发送域.
- 选择事件类型, 例如
delivered或者说permanent_fail。 。 。 。 - 添加完整的PortPreviewHTTPS终点.
- 重复您所支持的每个事件类型 。
- 发送测试或真实消息并检查本地请求和应用程序日志.
Mailgun当同一事件在账户和域级配置时,会复制同一事件的同名URL,但不同的URL可以各自收到副本. 父母账户的继承也会导致交付到多个不同的终点。 审查官员 配置规则 将每多送的货都归结为重复
Mailgun 签名核查工作如何进行
这个 signature 对象包含:
timestamp: 以秒计的Unix时间.token:随机生成出50个字符串.signature:十六进制HMAC文摘.parent-signature:可选择从子账户中为某一事件出面,允许对照Mailgun描述的主要账户关系进行验证.
对于正常的账户签名,计算 HMAC-SHA256(signingKey, timestamp + token)。没有分隔符,并且event-data JSON不是此所记录的Mailgun Send计算的一部分。 在检查等长后将解码的字节与计时安全函数相比较. 一个平地 === 比较比较比较简单,但时间安全比较是安全生产默认。
一个真实的HMAC证明持有签名密钥的一方生成了签名. 无法证明此送出没有被重播 。 Mailgun 特别推荐将信使绑起并用同一种信使拒绝随后的请求 。 时间戳检查限制捕获的有效请求持续多久仍然有用。 使用两个控件:一个独特的符号约束来重放和一个合理的新鲜度时间窗口.
重复交付和效果
保持两个持久的独有性限制:一个用于签名标志,一个用于Mailgun event-data.id。令牌捕捉到相同签名的重放。 如果同一事件出现在另一个有效的交付上下文中,事件ID会保护业务逻辑. 按提供者和账户或环境命名空间 。
async function acceptOnce({ eventId, replayToken, payload }) {
await db.transaction(async (tx) => {
const tokenWasNew = await tx.webhookTokens.insertIfAbsent({
provider: 'mailgun',
token: replayToken,
});
if (!tokenWasNew) return;
const eventWasNew = await tx.webhookEvents.insertIfAbsent({
provider: 'mailgun',
eventId,
receivedAt: new Date(),
});
if (!eventWasNew) return;
await tx.jobs.enqueue({
type: 'process-mailgun-event',
payload,
});
});
}
后面的插入(if-accessed)操作带有数据库独有的索引;在同时交付的情况下,读取后再读取一个插件即为多种族. 解析记录和队列任务 。 然后迅速承认,让一个工人更新消息状态,触发警报,或者同步一个CRM. 见 重试和自强指南 当队列和企业数据库无法共享交易时,用于替代。
Mailgun 响应代码和重试行为
Mailgun当前的Send Webhook文档给出了三个重要结果:
- 200 个成功 : Mailgun 将"网络hook POST"视为成功,不重试.
- 406 无法接受 : Mailgun将"POST"视为被否决,不重试.
- 任何其他代码 : Mailgun在8小时之内,在5分钟、10分钟、15分钟、1小时、2小时和4小时进行重复。
交付通知例外事项:不保证每类活动都遵循一般重试时间表。 检查最近 自动重试文档 当交付保证 影响你的设计。
使用 406 只用于您故意永久拒绝的请求, 如无效的签名或外部策略重放 。 使用500或503来进行瞬态数据库和队列失败,因此符合条件的webhook类型可以重试. 只有在持久接受后才能返回200. 如果进程退出,在启动未跟踪背景工作的同时返回200会丢失事件.
解决本地问题 Mailgun webhoks
计算出的 HMAC 不匹配
确认您正在使用 Webhook Signing Key,而不是 API 密钥、 SMTP 密码或 提醒签名密钥。 将签名对象的时间戳和符号合并,不设划界符。 制作一个小写十六进制 SHA-256 文摘. 同时确认您的框架没有重新命名连字符 event-data 属性;括号表示避免了这一错误。
处理器接收窗体字段而不是当前 JSON
请检查哪些 Mailgun 特性和端点版本生成请求 。 不要盲目将遗留的有效载荷应用到当前的 Send webhook 。 日志内容类型,顶级字段名称,以及正在开发的没有日志消息内容或秘密的机身长度,然后执行您账户和集成的有记录的合同.
Mailgun 继续重试
检查通过电线发送的实际状态. 数据库输入后的例外情况可能使回复变为500个,从而引起另一次尝试。 这就是为什么事件标识和符号插入必须独特和持久。 如果请求永久无效,则返回406;如果失败是短暂的,则修复服务并允许重试行为工作.
没有事件到达本地主机
确认URL附在正确的账户或域上,并附在正在生产的确切事件类型上. A级 delivered 无法接收 URL opened 事件。 请检查url=值 (帮助) 本地进程和隧道仍在运行,配置路径是 /webhooks/mailgun。跟踪 本地 Webhook 调试指南 将提供者配置与路由错误和应用错误分开。
安全检查清单
- 在信任或记录前验证 HMAC
event-data。 。 。 。 - 将签名密钥保存在一个秘密商店中,并通过可控部署来旋转;从不在客户端代码中曝光.
- 采用时间安全消化剂比较,时间戳政策,并持久地对符号形成独特的制约.
- 在排队前验证事件类型和需要的字段 。 将收件人地址,对象,存储 URL 和用户变量视为敏感数据.
- 只接受 POST, 盖身大小, 使用 HTTPS, 和速率限制失败而不屏蔽合法 Mailgun 重试 。
- 不通过临时的公有来源曝光无关的本地管理员或调试端点 。
- 当测试结束时,取出临时URL并配置稳定的生产终点.
Mailgun 也可以在webhook请求中记录一个可选的TLS客户端证书,当接收您的服务器拥有有效的TLS时. 这可以提供运输级别的验证,但不能取代有效载荷HMAC验证、重放控制和应用程序授权。 根据你的威胁模式 层层控制
生产接受试验
- 提供有效的签名固定装置,确认一个持久事件加200个答复。
- 在不更改签名的情况下更改指使并确认一个没有事件写入的406.
- 重放准确有效的身体,确认没有第二次工作或副作用.
- 在您配置的窗口外发送带有时间戳的有效签名, 并验证预定的拒绝 。
- 强制一个临时数据库出错,确认一个非-200/non-406响应,然后恢复数据库并验证一个成功接受.
- 由于有效载荷字段和重试预期值不同,因此每次练习都配置了Mailgun事件类型.
一旦这些试验通过,在生产中使用同样的核查和分解路径。 将HMAC比较和秘密处理的供应商独立解释改为: Webhook 签名验证指南。 。 。 。
