Lemon Squeezyのウェブフックをローカルホストでテストするには、アプリを起動し、ウェブフックのルートを公開されたHTTPSトンネルで公開し、設定 → ウェブフックでテストモードのウェブフックを作成し、生のリクエストボディを確認してください。 X-Signature ヘッダー。 実際の注文データにはテストチェックアウトを使用し、ライフサイクルイベントのように待つのが難しいものにはLemon Squeezyのサブスクリプションシミュレーションコントロールを使用してください。
正しいローカルセットアップの例
Lemon SqueezyはHTTPを送信します POST そのインフラストラクチャからなので、届くことができません localhost あなたのコンピュータ上で。このトンネルはパブリックTLSを終了し、変更されていないメソッド、ボディ、およびヘッダーをローカルルート(例えば)に転送します http://localhost:3000/api/webhooks/lemonsqueezy。
完全なテストは4つの部分で構成されます:Lemon Squeezyテストモード、公開コールバックURL、ダッシュボードとローカル環境で同じ署名シークレット、そしてサブスクリプションやライセンス状態を変更する前に検証するハンドラー。これは、コピーしたJSONを投稿するよりも有用です curl実際の配達は、ルーティングとプロバイダーが生成した署名の両方をテストするためです。
ウェブフックを作成して、テストイベントを送信する
- アプリをポート3000で実行し、ルートが受け付けることを確認してください
POST。 - 〜でトンネルを開始する
npx portpreview 3000。 - レモンスクイージーダッシュボードをテストモードに切り替えてください。
- 設定を開く → Webhooks に追加
https://your-subdomain.portpreview.dev/api/webhooks/lemonsqueezy。 - 強力なランダム署名シークレットを生成し、次のように保存します
LEMONSQUEEZY_WEBHOOK_SECRET、そしてあなたのアプリケーションが消費するイベントのみを選択してください。 - テスト購入を作成するか、利用可能なテストモードのサブスクリプションシミュレーションアクションを使用し、その後、配信ログとローカルの応答を確認してください。
公式の Lemon Squeezy ウェブフックドキュメント 設定ページから最近のペイロードを確認したり、記録されたウェブフックを再送信したりできます。再送信は回帰テストに有用ですが、アプリケーションはそれを二重のビジネスイベントではなく、重複として扱う必要があります。
確認する X-Signature JSONを解析する前に
Lemon Squeezyは、署名用シークレットを使用してリクエストペイロード上でHMAC-SHA256ダイジェストを計算し、16進ダイジェストを送信します X-Signature. 正確な生のボディバイトの上でダイジェストを計算し、同じ長さのバッファを定数時間の関数で比較します。主な 署名リクエストのドキュメント Node、PHP、Python、Ruby の例を含みます。
// app/api/webhooks/lemonsqueezy/route.ts
import crypto from 'node:crypto';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = process.env.LEMONSQUEEZY_WEBHOOK_SECRET;
if (!secret) return new NextResponse('server misconfigured', { status: 500 });
const rawBody = await request.text();
const suppliedHex = request.headers.get('x-signature') ?? '';
if (!/^[0-9a-f]{64}$/i.test(suppliedHex)) {
return new NextResponse('invalid signature', { status: 401 });
}
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest();
const supplied = Buffer.from(suppliedHex, 'hex');
if (supplied.length !== expected.length ||
!crypto.timingSafeEqual(supplied, expected)) {
return new NextResponse('invalid signature', { status: 401 });
}
const event = JSON.parse(rawBody);
await acceptEvent(event);
return NextResponse.json({ received: true });
}
電話しないで request.json() まず。結果として得られたオブジェクトを再シリアル化すると、些細に見えるバイトが変更され、HMACが壊れる可能性があります。長さのチェックも重要です。なぜなら、Nodeの timingSafeEqual バッファの長さが異なると例外をスローします。Lemon Squeezyの公式 Next.js ウェブフックチュートリアル 同じ生体順序に従う。
データベースを更新する前にペイロードを理解してください
そのボディはJSON:APIリソースオブジェクトです。 meta.event_name その出来事を特定する、 meta.custom_data チェックアウトを通じて渡されたデータを運び、そして data 注文、サブスクリプション、またはライセンスキーのリソースを含みます。Lemon Squeezyも送信します X-Event-Name; 署名付き本文を権威のある入力として扱い、診断ルーティングにはヘッダーを使用します。
注文と顧客の身元
〜のために order_created購入をチェックアウトのカスタムデータでサーバー生成の識別子を使用して内部ユーザーにマッピングします。所有権を確認せずにクライアント提供のユーザーIDを信用しないでください。Lemon SqueezyのリソースIDとバリアントIDを保存し、後でのサブスクリプションや返金イベントを照合できるようにします。
サブスクリプションはステートマシンです
取っ手 subscription_created、 subscription_updated、 subscription_cancelled、 subscription_resumed、そして subscription_expired 明確に区別された移行として。キャンセルは、請求期間の終了までアクセスが有効のままであることを意味する場合があります。期限切れは、サブスクリプションが終了したというより強い信号です。ブール値を切り替えるのではなく、日付とステータスをモデル化してください。
支払いの失敗と回復
テストモードでサポートされている場合、テストで失敗した後に回復した更新フロー。支払いの失敗が必ずしもアクセスを直ちに取り消すわけではありません。製品ポリシーで定義された猶予期間を適用し、その後の支払いまたはサブスクリプションのイベントによってアカウントを最新のプロバイダー状態に収束させてください。
リトライには冪等な処理が必要です
レモンスクイージーによると ウェブフックリクエスト参照、a 200 応答マークは成功として記録されます。他のステータスは、約5秒、25秒、125秒と表示される指数関数的な遅延で、さらに最大3回まで再試行されます。ダッシュボードの再送は、同じ論理イベントが複数回届く別の理由を生みます。
安定した署名付きペイロードフィールドから冪等性キーを作成します。実用的な方法としては、WebhookのリソースID、イベント名、イベントタイムスタンプに対して一意のデータベース行を作成するか、専用のイベントIDが存在しない場合は生の署名付き本文のハッシュを使用します。その受領を挿入し、アプリケーションの状態を1つのトランザクションで更新します。返却します 200 以前に完了した重複のために。
BEGIN;
INSERT INTO webhook_receipts (fingerprint, received_at)
VALUES ($1, now())
ON CONFLICT DO NOTHING;
-- Continue only if one row was inserted.
UPDATE subscriptions SET provider_status = $2 WHERE provider_id = $3;
COMMIT;
メールの配信や遅いサードパーティAPI呼び出しを確認パスの一部にしないでください。同じトランザクションでジョブを保存し、応答を返し、サイドエフェクトはワーカーに処理させます。より広範な ウェブフックの再試行と冪等性ガイド このパターンをカバーします。
Lemon Squeezyウェブフックのローカルホストテストのトラブルシューティング
アプリにリクエストが届かない
トンネルがまだ稼働していること、コールバックにルートのサフィックスが含まれていること、そしてあなたのフレームワークにその正確なパスでPOSTハンドラーがあることを確認してください。パブリックエンドポイントを自分で呼び出してください。トンネルの502はローカルポートを示し、フレームワークの404はルートの配置を示します。
すべての署名は無効です
テストモードとライブモードが異なるWebhookレコードやシークレットを使用していないことを確認してください。本文はテキストまたはバイトとして一度だけ読み取り、本文やシークレットをトリミングせず、グローバルJSONミドルウェアが最初に実行されないことを確認してください。次を検査してください X-Signature 到着しましたが、署名秘密を印刷してはいけません。
配信は再試行を続けています
レモンスクイージーは期待します 200ログインページへのリダイレクトではありません。署名の検証を維持しつつ、CSRFおよびユーザーセッションのミドルウェアからWebhookパスを除外してください。データベースのエラーをキャッチし、作業を短く保ち、処理されるすべてのイベントブランチで成功のレスポンスが実際に返されるようにしてください。
サブスクリプションのシミュレーションは利用できません
シミュレーションのコントロールには、適切なテストモードのサブスクリプションデータが必要です。一部の支払いシミュレーションには、更新履歴が必要です。まず必要なテストサブスクリプションまたはテスト購入を作成し、その後、Lemon Squeezyで説明されているサブスクリプションアクションメニューを使用してください。 ウェブフック開発者ガイド。
セキュリティおよび本番稼働チェックリスト
- ランダムな署名用シークレットを生成し、環境設定に保存し、もし漏れた場合は回転させます。
- JSON解析、データベースへの書き込み、ライセンス生成、またはアクセス変更の前に、生のボディー上でHMACを検証してください。
- タイミング攻撃に安全な比較を使用し、欠落または不正な形式の署名を拒否してください。
- テストモードとライブのウェブフックの秘密情報および記録は分けて保持してください。
- POSTのみを許可し、適切なボディサイズの制限を課し、無効なリクエストをレート制限し、ログから顧客データを削除してください。
- トンネルを本番用のURLに置き換える前に、重複テスト、順序が入れ替わったライフサイクルイベント、データベースのダウンタイム、およびタイムアウト時の復旧を確認してください。
正常に進むチェックアウトは、単に一つのイベントが到達したことを示すだけです。本番対応の統合は、署名の拒否、重複の安全性、ライフサイクルの収束、および非200のレスポンスからの回復を証明します。基礎となる暗号化の落とし穴については、次を参照してください。 署名確認ガイド。
