所有文章
结账、订单、退款和订阅事件通过安全中继传送到本地 Webhook 处理程序。
Polarwebhooksbillinglocal testing

使用 Polar CLI 在本地测试 Polar Webhook

要在本地测试 Polar webhook,请运行您的 webhook 处理程序,安装并验证 Polar CLI,然后使用 polar listen http://localhost:3000/api/webhooks/polar 将签名事件直接转发到您的计算机。 将监听器打印的临时秘密复制到 POLAR_WEBHOOK_SECRET,在处理原始主体之前对其进行验证,并触发沙箱结帐、订单、退款或订阅事件。

为什么 Polar CLI 是最简单的本地工作流程

正常的生产 Webhook 端点必须可通过 HTTPS 公开访问。 Polar 提供专门构建的本地侦听器,因此您无需创建永久端点或将临时回调 URL 重复粘贴到仪表板中。 CLI 连接到您的组织,显示会话签名密钥,接收事件,并将它们转发到您提供的本地 URL。

极地官方 本地 webhook 文档 用途 polar listen http://localhost:3000/。当您的应用程序处理以下路径下的 Webhook 时,将其指向完整路径,例如 /api/webhooks/polar。这可以在生产之前捕获路径错误,并保留您打算部署的相同应用程序处理程序。

安装 CLI 并开始监听

  1. 启动您的应用程序并确认其 POST 路由在本地可用。
  2. 使用官方文档中的命令安装 Polar CLI。
  3. 运行 polar login 并授权正确的帐户。
  4. 开始 polar listen http://localhost:3000/api/webhooks/polar.
  5. 选择拥有您的沙盒产品的组织。
  6. 将显示的会话密钥复制到 POLAR_WEBHOOK_SECRET 并重新启动您的应用程序(如果其环境仅在启动时加载)。
  7. 触发沙箱操作并将 CLI 传送状态与本地日志进行比较。

侦听器机密是本地会话的一部分。不要假设它等于仪表板端点机密或重复使用生产值。如果您重新连接并收到不同的密钥,请在再次测试之前更新本地环境。

创建 Next.js App Router 处理程序

Polar 遵循标准 Webhooks 规范。一次交付包括 webhook-id, webhook-timestamp, 和 webhook-signature。验证必须使用准确的原始请求正文;呼叫 request.json() 首先并再次序列化对象可以更改带符号的字节。

// app/api/webhooks/polar/route.ts
import { Webhook } from 'standardwebhooks';

export const runtime = 'nodejs';

export async function POST(request: Request) {
  const rawBody = await request.text();
  const secret = process.env.POLAR_WEBHOOK_SECRET;
  if (!secret) {
    return new Response('Webhook secret is not configured', { status: 500 });
  }

  const headers = {
    'webhook-id': request.headers.get('webhook-id') ?? '',
    'webhook-timestamp': request.headers.get('webhook-timestamp') ?? '',
    'webhook-signature': request.headers.get('webhook-signature') ?? '',
  };

  try {
    const encodedSecret = Buffer.from(secret.trim(), 'utf8').toString('base64');
    const payload = new Webhook(encodedSecret).verify(rawBody, headers);
    await acceptPolarEvent(headers['webhook-id'], payload);
    return Response.json({ received: true });
  } catch {
    return new Response('Invalid signature', { status: 403 });
  }
}

极地的 交货文件 出现了一个常见的自定义验证陷阱:标准 Webhooks 库需要 Base64 编码的秘密,而 Polar 提供普通的秘密字符串。 Polar SDK 帮助程序会自动处理此转换。使用时 standardwebhooks 直接对完整的 Polar 秘密进行 Base64 编码,如上所示。

首选官方框架适配器(如果可用)

Polar 发布了将签名验证与类型化负载处理程序相结合的适配器。官方 快速适配器文档 为结账、订单、退款、福利和订阅事件提供精细的回调。适配器减少了错误地实现标头解析或秘密编码的机会。

import express from 'express';
import { Webhooks } from '@polar-sh/express';

const app = express();
app.use(express.json());
app.post('/webhooks/polar', Webhooks({
  webhookSecret: process.env.POLAR_WEBHOOK_SECRET,
  onOrderPaid: async (event) => {
    await queueOrderPaid(event.data);
  },
  onSubscriptionActive: async (event) => {
    await queueSubscriptionActive(event.data);
  },
}));
app.listen(3000);

请遵循您安装的版本的适配器记录的中间件顺序。如果您使用通用验证器,请显式保留原始主体。不要将读取流的适配器与首先使用流的中间件混合在一起。

您应该测试哪些极地事件?

结账活动

结帐事件有助于在会话成为付费订单之前对其进行跟踪。测试成功完成以及放弃或更新的状态,而不会仅仅因为创建了签出而授予访问权限。付费订单或活跃订阅应仍然是权威的权利信号。

订单已付款并已退款

对于订单支付活动,将 Polar 客户和产品映射到您的内部账户,然后一次性授予购买的权益。根据您的退款政策,退款事件应仅撤销受影响的权利。存储提供商 ID,以便支持人员可以与 Polar 协调活动。

订阅生命周期事件

测试您的产品支持的创建、激活、更新、取消、撤销以及任何逾期行为。取消请求可能并不意味着访问立即结束。存储状态和生效日期,而不是将生命周期缩短为一个 is_active 标志。

福利补助金

如果您的集成使用 Polar 权益,请独立于计费事件测试补助金创建、更新和撤销。使处理程序收敛于所需状态,以便两次接收相同的授权不会提供重复的资源。

使每个事件幂等

网络交付不是一次性事务。处理程序可以提交其数据库工作并丢失响应,导致发送方重试。使用 webhook-id 作为唯一的交付密钥,并在更新应用程序状态的同一事务中声明它。

await db.transaction(async (tx) => {
  const firstDelivery = await tx.webhookReceipts.insertIfAbsent({
    provider: 'polar',
    deliveryId: webhookId,
  });
  if (!firstDelivery) return;

  await applyPolarEvent(tx, payload);
  await tx.outbox.enqueue('polar-event-accepted', { webhookId });
});

对于已完成的副本返回 2xx 响应。不要仅按客户或订阅 ID 进行重复数据删除,因为许多合法事件都针对同一资源。的 webhook 重试和幂等性指南 更详细地介绍了收件箱和发件箱模式。

保持致谢工作简短

验证请求,将其保留或将持久作业排入队列,然后返回成功。电子邮件、许可证生成、存储库访问、分析和第三方 API 调用应异步运行。快速确认可减少重试,而持久写入可防止进程在返回后退出时事件消失。

未知事件类型应在有效签名验证后进行记录和确认。 Polar 可以随着时间的推移添加事件类型;添加每个不熟悉的值会将无害的模式添加转换为重复的交付失败。

排查 Polar webhook 本地主机故障

CLI 连接但路由返回 404

将完整的本地路由传递给 polar listen,不仅仅是港口。在 Next.js App Router 中,确保该文件已命名 route.ts 和出口 POST。浏览器 GET 返回 404 并不能证明 POST 路由丢失,因此请使用 curl -X POST.

每次请求都会返回403

确认应用程序加载了当前 CLI 会话打印的机密。保留原始正文和所有三个标准 Webhooks 标头。如果您使用通用库,请对 Polar 秘密进行一次 Base64 编码;如果您使用 Polar SDK 帮助程序,请传递原始密钥并让 SDK 处理编码。

结帐后没有出现任何事件

验证 CLI 是否连接到发生操作的同一组织和环境。保持监听器终端打开,使用沙箱资源,并确认您的操作确实达到您订阅的事件状态。

事件被处理两次

添加唯一约束 webhook-id 并返回重复项的成功。检查状态更新之后、响应之前是否发生异常。将缓慢的副作用转移到发件箱支持的工作人员身上。

旧的捕获请求验证失败

标准 Webhooks 验证包括限制重放攻击的时间戳。旧的捕获应该无法通过正常的验证窗口。对于业务逻辑测试,验证一次新的交付并在身份验证边界后面保存经过净化的有效负载固定装置。

生产前安全检查表

  • 使用单独的本地、沙箱和生产端点机密。
  • 在处理之前验证原始正文、时间戳、交付 ID 和签名。
  • 将 API 令牌和 Webhook 机密保存在仅服务器环境变量中。
  • 重复数据删除方式 webhook-id 并验证产品、组织和客户标识符。
  • 从共享日志中编辑客户电子邮件、计费数据、元数据和完整负载。
  • 应用请求大小限制并监视重复的无效签名。
  • 使用稳定的 HTTPS 端点替换本地侦听器,并在启用实时产品之前测试新的沙箱流。

Polar CLI 消除了部署循环,但不应消除生产控制。在本地启用签名验证、幂等性、事件过滤和持久处理,以便您测试的代码就是您发布的代码。有关独立于框架的验证详细信息,请阅读 webhook签名验证指南 和将军 本地调试工作流程.

常见问题

如何在本地测试 Polar webhook?
运行本地处理程序,验证 Polar CLI,使用完整的本地主机 Webhook URL 执行 Polar 侦听,将显示的机密复制到 POLAR_WEBHOOK_SECRET,并触发沙箱事件。
我是否需要用于 Polar webhook 的公共隧道?
使用 Polar 的 CLI 侦听器时不会。它将组织事件中继到您提供的本地主机 URL。普通的仪表板 Webhook 端点仍然需要可公开访问的 HTTPS URL。
为什么我的 Polar webhook 签名失败?
常见原因是使用旧的侦听器机密、在验证之前解析 JSON、省略标准 Webhooks 标头或在使用通用验证器时忘记对 Polar 机密进行 Base64 编码。
如何防止重复的 Polar webhook 处理?
将 webhook-id 存储在唯一的数据库约束下,并在同一事务中应用该事件。当交付 ID 已完成时返回成功。