salla-app-lifecycle について
Salla アプリ開発者向けに、Webhook の設定とインストール、認可、サブスクリプション、アンインストールのイベント処理を案内するスキルです。商店ごとの冪等な更新、署名検証、非同期処理の信頼性を重視します。
主な機能
- 01ライフサイクルイベントのWebhook設定
- 02インストール・更新・削除の処理
- 03トライアルとサブスクリプション状態の処理
- 04商店単位での冪等なレコード更新
- 05署名検証と非同期リトライの案内
活用シーン
- 01Salla アプリのインストールフロー実装
- 02商店トークンと契約状態の同期
- 03アンインストール後のデータ整理
MCP 対応
Salla Partners MCP の `salla_apps` ツールでWebhookの受信先を設定し、アプリ状態を確認します。ライフサイクルイベントはそのURLに自動配信されます。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sallaapp/ salla-partners-agent-kit --skill salla-app-lifecycleインストール後、Claude Code で/salla-app-lifecycleを入力して読み込まれたか確認します。
同梱ファイルreferences/lifecycle-payloads.md
詳細
Salla アプリのライフサイクルフロー
各マーチャントとアプリの関係を、インストールからトライアル、サブスクリプション、アンインストールまで、バックエンドと同期させます。手順に沿って進め、次に進む前に各ゲートを完了してください。ステップ 1 では Salla Partners MCP を使ってWebhook の受信先を設定します。ステップ 2~6 は、作成するランタイムハンドラーです。
ツールと MCP
App Events のドキュメント(https://docs.salla.dev/421413m0.md)は、すべてのライフサイクルペイロードの形式に関する信頼できる情報源です。**Salla Partners MCP はアクションを実行します**。
| ツール | アクション | 内容 |
|---|---|---|
salla_apps | connect / get | webhook_url を設定(ライフサイクルのアプリイベントは自動的に配信されます)/アプリの状態を確認 |
ライフサイクルイベントは
app.*イベントであり、サブスクライブ呼び出しは不要です。 アプリはデフォルトで独自のアプリイベントをサブスクライブするため、app.installed、app.store.authorize、app.updated、app.uninstalled、app.trial.*、app.subscription.*、app.settings.updatedはすべて、発生するとすぐにwebhook_urlに自動配信されます。 これらのイベントに対してsalla_events action=subscribeを呼び出す必要はありません。このアクションはアプリ以外の(ストアの)イベント(order.*、product.*、…)にのみ使用します → salla-webhooks。
すべてのイベントは、標準エンベロープ(
event、merchant、created_at、data)に含まれて、単一の Webhook エンドポイントに届きます。署名を検証し、速やかに200で応答してから、非同期で処理してください。常にmerchantをキーにしてアップサートしてください。 前提条件:署名検証+冪等性+速やかな 200 応答 → salla-webhooks、app.store.authorizeからのトークン永続化 → salla-app-auth。
イベントカタログ(リファレンス)
| イベント | 発生時期 | 対応内容 |
|---|---|---|
app.installed | 初回インストール | マーチャントのリソースをプロビジョニングし、デフォルト値を設定します |
app.store.authorize | インストール または トークン更新 | マーチャントごとに access_token + refresh_token + 有効期限を保存/更新します |
app.updated | マーチャントがアプリを更新 | Salla は直後に app.store.authorize を発行するため、新しいトークンが届くまで待ちます |
app.settings.updated | マーチャントがアプリ設定を変更 | data.settings を適用します — スキーマと検証は salla-app-settings が担当します |
app.trial.started | トライアル開始 | トライアル機能を有効にします |
app.trial.expired | アップグレードせずにトライアル終了 | アクセスを制限します |
app.trial.canceled | トライアルキャンセル | アクセスを制限します |
app.subscription.started | 有料プラン またはアドオン の有効化 | 機能のロックを解除します。data.item_type に応じて処理を分岐します |
app.subscription.renewed | プラン/アドオンの更新 | 有効であることを確認し、新しい data.end_date / renew_date を保存します |
app.subscription.expired | プラン/アドオンの期限切れ | アクセスを制限し、マーチャントに通知します |
app.subscription.canceled | プラン/アドオンのキャンセル | アクセスを制限します |
app.feedback.created | マーチャントがレビューを投稿 | 評価とコメントを記録します |
app.subscription.*はプランとアドオンの両方で発行されます。data.item_type("plan"|"addon")で区別できます。app.trial.*のペイロードはサブスクリプションよりも 小さく(subscription_id、item_type、価格などを含みません)、詳細はリファレンスを参照してください。サブスクリプションの完全な ペイロードとプラン状態の処理 → salla-app-billing。イベントごとの完全な JSON (https://docs.salla.dev/421413m0.md に記載された正式な形式) → references/lifecycle-payloads.md。
ステップ 0 — 確認
開始前に、次の点を確認してください。
- アプリが対応するライフサイクルの状態はどれですか?(インストールのみですか、それともトライアルと サブスクリプションも含みますか?)
- インストール時に何をプロビジョニングし、アンインストール時に何をクリーンアップする必要がありますか (保持ポリシー/GDPR ポリシー)?
- プラン/トライアルの状態に基づいて機能を制限しますか?(制限する場合は、 salla-app-billing と組み合わせてください)
ステップ 1 — Webhook URL を設定する(ライフサイクルイベントは自動配信)
ライフサイクルイベントはアプリイベントです。アプリはデフォルトでこれらのイベントを購読しているため、webhook_url を設定するとすぐに届きます。購読の呼び出しは不要です。必要なのは、Partners MCP で受信先を設定することだけです。
- 受信先を設定します:
salla_apps action=connect、app_id、webhook_url、webhook_security_strategy: "signature"を指定します。connectでは署名シークレットは発行されません。Partner Portal(https://portal.salla.partners/apps/{app_id})で作成またはローテーションし、デプロイ前にsalla_apps action=get(webhook_secretフィールド)で現在の値を確認してください。シークレットマネージャー/KMS に保存してください(ソースやログには絶対に保存しないでください)。安全な保存と HMAC 検証は salla-webhooks が担当します。 - これで配信の設定は完了です:
app.installed、app.store.authorize、app.updated、app.uninstalled、app.trial.*、app.subscription.*、app.settings.updatedが自動的にその URL に配信されます。(salla_events action=subscribeはストアイベント(order.*、product.*、…)用です。ライフサイクル処理では不要です → salla-webhooks。)
salla_apps action=get と app_id を使えば、いつでもアプリの現在の設定を確認できます。
確認事項:「salla_apps action=connect で webhook_url を設定し(アプリイベントは自動配信され、購読の呼び出しは不要)、エンドポイントが 200 を返しますか?」
ステップ 2 — インストールフローを処理する
初回インストール時、Salla は app.installed と app.store.authorize を送信します。各ハンドラーは独立して冪等に処理してください。到着順序は保証されません。
すべてのハンドラーで、
merchantごとのレコードをアップサート(作成または更新)してください。app.trial.startedなど、あらゆるライフサイクルイベントがapp.store.authorizeより先に届く可能性があるため、レコードがまだ存在しない場合があります。各ハンドラーは、merchantをキーにして、レコードがなければ作成し、あれば更新します。これにより、どのような到着順序でも、再インストール時でも、すべてのハンドラーを安全に処理できます。
if (payload.event === "app.installed") {
// Provision: create the merchant row, seed defaults, queue a welcome step.
await db.merchants.upsert({
where: { id: payload.merchant },
create: {
id: payload.merchant,
status: "installed",
installedAt: new Date(),
},
update: { status: "installed" },
});
}
if (payload.event === "app.store.authorize") {
// Persist tokens — see salla-app-auth for the full upsert + refresh rules.
// Encrypt access_token/refresh_token at rest and keep them out of logs.
const { access_token, refresh_token, expires, scope } = payload.data;
await db.merchants.upsert({
where: { id: payload.merchant },
create: {
id: payload.merchant,
accessToken: access_token,
refreshToken: refresh_token,
tokenExpiresAt: new Date(expires * 1000),
scope,
},
update: {
accessToken: access_token,
refreshToken: refresh_token,
tokenExpiresAt: new Date(expires * 1000),
scope,
},
});
}
バージョン管理されたマイグレーションツールを使って、merchant/token テーブルを管理してください(バージョン管理された
ALTER TABLE)。テーブルがすでに存在する場合に作成時の処理がスキップされる問題を避け、列の追加や変更に伴ってスキーマをコードと常に一致させられます。
確認事項:「デモストアのインストール時に、到着順序にかかわらず merchant レコードが作成され、トークンが保存されますか?」
ステップ 3 — 更新フローを処理する
merchant がアプリを更新すると、Salla は app.updated を送信し、その直後に新しいトークンを含む app.store.authorize を送信します。
app.updatedは通知としてのみ扱ってください。ペイロードにトークンが含まれることを期待しないでください。- 既存の
app.store.authorizeハンドラーで新しいトークンを保存してください。
確認事項:「アプリの更新時に、更新ハンドラーではなく authorize ハンドラーを通じて保存済みトークンが更新されますか?」
ステップ 4 — アンインストール時のクリーンアップを処理する
app.uninstalled は、merchant がアプリを削除したことを意味します。クリーンアップは App Store の要件です。データ保持/GDPR ポリシーに従って処理してください。
if (payload.event === "app.uninstalled") {
await jobs.enqueue("cleanupMerchant", { merchantId: payload.merchant });
// In the job: revoke cached tokens, delete or anonymize PII, cancel scheduled work,
// mark merchant status = "uninstalled". Keep only what your policy/law requires.
}
保存するデータと、アンインストール時にそのデータをどのように削除するかを文書化してください。提出時に審査されます。
確認事項:「アンインストール時にクリーンアップがキューに登録され、ジョブによってトークンが失効され、PII が削除または匿名化されますか?」
ステップ 5 — トライアルとサブスクリプションのイベントを処理する
| イベント | 効果 |
|---|---|
app.trial.started | トライアルプランの機能を有効化し、トライアル終了日を記録する |
app.trial.expired | 購読されるまでダウングレード/制限する |
app.trial.canceled | 直ちに制限する |
merchant は通常、trial.started →(コンバージョンした場合は subscription.started、しなかった場合は trial.expired)の順に進みます。ここでは最小限のサブスクリプション処理を示します。完全なロジックについては salla-app-billing を参照してください。
if (payload.event === "app.subscription.started") {
const { item_type, item_slug, end_date } = payload.data; // item_type: "plan" | "addon"
if (item_type === "addon") {
await entitlements.activateAddon(payload.merchant, item_slug, end_date);
} else {
await merchants.activatePlan(payload.merchant, payload.data);
}
}
テストストアのサブスクリプションを実際の収益として扱わないよう、data.store_type(development | demo | live)を確認してください。
確認事項:「トライアルとサブスクリプションのイベントによって merchant の保存済みステータスが遷移し、item_type に応じて分岐しますか?」
ステップ 6 — 堅牢化:信頼性と状態マシン
上記のすべてのハンドラーで必ず実施すること:
- まず署名を検証する(HMAC-SHA256、タイミングセーフ)。検証に失敗した場合は拒否します。
- すぐに
200で応答してから、非同期で処理する — Salla は配信に失敗すると再試行します。タイムアウトと再試行ポリシーは salla-webhooks が管理します。 - 冪等性を確保する — ペイロード内の安定した識別子(例:
subscription_id)または生の本文のハッシュをキーにします。created_atは秒単位の精度のため、${merchant}:${event}:${created_at}だけでは衝突する可能性があります。 - アップサートする — 同じマーチャントが再インストールし、再認可できるようにします。
- 非同期ジョブの失敗に対処する —
200はすでに送信済みのため、キュー内のワーカーで例外が発生しても Salla には見えません。失敗したジョブをデッドレターキューに送り、バックオフ付きで(冪等に)再試行し、再試行を使い果たしたらアラートを送信します。これにより、インストールやアンインストールが処理されずに見過ごされるのを防ぎます。
推奨するマーチャントの状態マシン — マーチャントごとに status を永続化し、上記のハンドラーから状態を遷移させます:
app.installed
│
▼
┌──────► installed ──────┐
│ │ │ app.trial.started
│ │ ▼
│ │ trial ──── app.trial.expired/canceled ──► restricted
│ │ │ app.subscription.started
│ ▼ ▼
│ active ◄── app.subscription.renewed
│ │
│ │ app.subscription.expired/canceled
│ ▼
│ restricted
│ │ app.subscription.started (merchant re-purchases)
└───────────┘
│ app.uninstalled (from any state)
▼
uninstalled (run cleanup)
機能の利用可否は status とアドオンの利用権(salla-app-billing を参照)に基づいて判定します。
確認事項:「すべてのハンドラーが署名を検証し、すぐに 200 を返し、冪等性を確保したうえで、状態マシンに沿って status を更新していますか?」
注意すべき点
簡単なテストでは問題なく見えても、実際のマーチャントでは成り立たないライフサイクル配信に関する思い込みです。次のいずれかを前提としている場合は、該当するステップを読み直してください。
| 思い込み | 誤りである理由 |
|---|---|
"app.installed は app.store.authorize より先に届く。" | イベントの順序は保証されていません。どのイベントが先に届くこともあります。各ハンドラーを独立させて冪等にし、merchant をキーにアップサートしてください(ステップ 2)。 |
"app.updated に新しいトークンが含まれている。" | これはシグナルにすぎません。トークンは、その後に届く app.store.authorize に含まれています。更新ペイロードからトークンを読み取らないでください(ステップ 3)。 |
"insert で問題ない — インストールは一度しか行われない。" | マーチャントは再インストールし、再認可します。挿入のみでは例外や重複が発生するため、常に merchant をキーにアップサートしてください(ステップ 2、6)。 |
| "非同期ジョブがキューに入っているので、処理は安全だ。" | 200 はすでに送信済みです。ワーカーで例外が発生しても Salla には見えません。デッドレターキューに送り、冪等に再試行し、アラートを送信してください(ステップ 6)。 |
"${merchant}:${event}:${created_at} は一意なキーだ。" | created_at は秒単位の精度のため、同じ種類のイベントが同じ秒に 2 件発生すると衝突します。subscription_id または生の本文のハッシュを追加してください(ステップ 6)。 |
| "test-store のサブスクリプションイベントを売上として計上する。" | data.store_type を確認してください。実際のストアは live のみです。開発用またはデモ用のインストールを含めると、売上と利用権が不正確になります(ステップ 5)。 |
| "アンインストールはステータスフラグを切り替えるだけだ。" | クリーンアップ(トークンの失効、個人情報の削除/匿名化)は、審査時に確認される App Store の要件です。処理全体をジョブとしてキューに入れてください(ステップ 4)。 |
主なリソース
| リソース | URL |
|---|---|
| App Events のドキュメント | https://docs.salla.dev/421413m0.md |
| Webhook(セキュリティ) | salla-webhooks スキル |
| トークンの処理 | salla-app-auth スキル |
| プラン/トライアルの状態 | salla-app-billing スキル |
| Partners Portal | https://salla.partners |
| Telegram コミュニティ | https://t.me/salladev |