所有文章
如何在 localhost 测试 Mailgun Webhook
Mailgunemail webhooksHMAC verificationlocalhost

如何在 localhost 测试 Mailgun Webhook

要在本地主机上测试 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. 选择与应用程序相匹配的最窄范围。

  1. 为想要的账户打开Webhooks区域或发送域.
  2. 选择事件类型, 例如 delivered 或者说 permanent_fail。 。 。 。
  3. 添加完整的PortPreviewHTTPS终点.
  4. 重复您所支持的每个事件类型 。
  5. 发送测试或真实消息并检查本地请求和应用程序日志.

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验证、重放控制和应用程序授权。 根据你的威胁模式 层层控制

生产接受试验

  1. 提供有效的签名固定装置,确认一个持久事件加200个答复。
  2. 在不更改签名的情况下更改指使并确认一个没有事件写入的406.
  3. 重放准确有效的身体,确认没有第二次工作或副作用.
  4. 在您配置的窗口外发送带有时间戳的有效签名, 并验证预定的拒绝 。
  5. 强制一个临时数据库出错,确认一个非-200/non-406响应,然后恢复数据库并验证一个成功接受.
  6. 由于有效载荷字段和重试预期值不同,因此每次练习都配置了Mailgun事件类型.

一旦这些试验通过,在生产中使用同样的核查和分解路径。 将HMAC比较和秘密处理的供应商独立解释改为: Webhook 签名验证指南。 。 。 。

常见问题

Mailgun 能否向本地主机发送webhoks?
Mailgun 无法直接到达本地主机 。 运行 `npx portpreview PORT`, 将您的webhook 路由附加到生成的 HTTPS 源头, 并为每个需要的 Mailgun 事件类型配置公共 URL 。
我该如何验证一个Mailgun Send的网签?
将有效载荷签名对象的时间戳和信使与无分隔符相接,使用Webhook Signing Key计算出HMAC-SHA256十六进制文摘,并使用时间安全比较法将其与所提供的签名相比较.
我该如何防止Mailgun网络呼喊重放攻击?.
将每个签名令牌保存在一个持久的独有约束下,并拒绝一个已经看到的令牌. 还要执行合理的时间戳政策,为合理的交货延误和操作要求留出足够的时间。
Mailgun 何时重试一个失败的 Webhook ?
Mailgun视200为成功,406为永久拒绝. 对于其他回复,除送货通知外,其他网络用户使用所记录的重试间隔时间约8小时,因此处理者必须是一能的.