Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

App Lifecycle

Every app on LaunchMyStore moves through the same lifecycle: a merchant discovers it in the marketplace, installs it (granting OAuth scopes), uses it (your code runs in their store), occasionally updates it (new version + new scopes), and eventually uninstalls it. This page is a single reference for every event your app will receive at each stage and what your handlers should do.

Lifecycle States

   ┌──────────────┐                  ┌──────────────┐
   │ Marketplace  │  install         │              │
   │   Listing    │ ───────────────▶ │  installed   │
   └──────────────┘                  │              │
                                     └──────┬───────┘

                       ┌──────── publish ───┤
                       │     new version     │
                       ▼                     │
                  ┌─────────┐                │
                  │ updated │                │
                  └────┬────┘                │
                       │                     │
                       └─────────────────────┘

                                            │ uninstall

                                     ┌──────────────┐
                                     │ uninstalled  │
                                     └──────────────┘
StateApp can call APIs?Webhooks delivered?Extensions render?
Listing onlyNoNoNo
InstalledYesYesYes
Scope-update pendingYes (old scopes)YesYes
UninstalledNo (revoked)Final app/uninstalled onlyNo

1. Discovery & Install

Merchants discover apps in the marketplace and click Install. This kicks off the OAuth flow.

OAuth flow (high level)

Merchant clicks "Install"

Browser → /apps/oauth/authorize?client_id=...&redirect_uri=...&scopes=...&state=...

Merchant approves scopes

Browser → <redirect_uri>?code=...&state=...

Your app: POST /apps/oauth/token { grant_type: 'authorization_code', code, ... }

Response: { access_token, refresh_token, expires_in, scopes }
See Authentication for the full code walkthrough.

Install rejection — per-shop function caps

If your app declares one or more functions in app.json and the merchant’s store already has the maximum number of active apps shipping that function type, both install paths reject the install with HTTP 409 Conflict:
  • Direct merchant install: POST /apps/:appId/install
  • OAuth code exchange: POST /apps/oauth/token with grant_type=authorization_code
Response shape:
{
  "status": 409,
  "type": "error",
  "message": "Cannot install: this store already has 1 active cart_transform function, and the per-shop limit is 1. Uninstall another cart_transform app before installing this one."
}
Your installer / OAuth callback should surface the message field directly — it names the offending function type and the current cap so the merchant knows what to uninstall. See Functions › Active-Install Caps Per Shop for the per-type limits.
Re-issuing tokens for an already-active installation never triggers the cap check — only new activations are counted. A previously-uninstalled app that the merchant is re-installing also passes the check as long as the cap still has headroom.

app/installed webhook

Fired immediately after the merchant completes OAuth. Use this to:
  • Provision merchant-side resources (DB row, default settings)
  • Send a welcome email
  • Sync initial data via the granted scopes
{
  "topic": "app/installed",
  "createdAt": "2026-05-06T12:00:00Z",
  "domainSlug": "merchant-store",
  "merchantId": "mer_01H...",
  "appId": "app_01H...",
  "data": {
    "installationId": "inst_01H...",
    "version": "1.5.0",
    "scopes": ["read_products", "write_orders"],
    "installedAt": "2026-05-06T12:00:00Z"
  }
}
The app/installed webhook fires after the OAuth redirect — your redirect handler should not assume any merchant-side state has been created yet. Either bootstrap your DB row inside the OAuth handler (synchronous, blocks the redirect) or render a “Setting up…” page that polls until the webhook completes.

2. Active Use

Once installed, your app can:
  • Call any /api/v1/* endpoint with the access token (subject to granted scopes)
  • Receive any webhook the merchant has subscribed (or that you registered at install time)
  • Have its extensions rendered into storefronts, checkouts, and admin pages
  • Have its functions executed during verifyCart and addClientOrder flows

Token refresh

Access tokens expire after 24 hours. Refresh tokens last 30 days. Always implement refresh:
async function getValidToken(installation) {
  if (Date.now() < installation.expiresAt - 60_000) {
    return installation.accessToken;
  }
  const r = await fetch('https://api.launchmystore.io/apps/oauth/token', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'refresh_token',
      client_id: process.env.LMS_CLIENT_ID,
      client_secret: process.env.LMS_CLIENT_SECRET,
      refresh_token: installation.refreshToken
    })
  });
  const tokens = await r.json();
  await storeTokens(installation.id, tokens);
  return tokens.access_token;
}
If the refresh token has also expired (30+ days of inactivity), the merchant must re-authorize.

3. Scope Updates (Version Publish)

When you publish a new app version that requires additional scopes, every installation gets a app/scopes_update event the next time it pulls the version.

app/scopes_update webhook

{
  "topic": "app/scopes_update",
  "createdAt": "2026-05-06T12:00:00Z",
  "data": {
    "installationId": "inst_01H...",
    "previousScopes": ["read_products"],
    "newScopes": ["read_products", "read_orders"],
    "addedScopes": ["read_orders"],
    "removedScopes": [],
    "version": "1.6.0"
  }
}
If addedScopes is non-empty, the merchant must re-authorize before the new scopes are usable. Show them an in-app banner with a re-auth link:
https://api.launchmystore.io/apps/oauth/authorize
  ?client_id=<your-client-id>
  &redirect_uri=<your-callback>
  &scopes=read_products,read_orders
  &state=<csrf-token>
Until they re-auth, calls to /api/v1/orders will return 403 missing_scope: read_orders.

4. Subscription / Billing Events

If your app charges for use, Stripe events flow through to your webhooks too:
TopicWhen
app/subscription_createdMerchant accepts a paid plan
app/subscription_updatedPlan changed, quantity changed
app/subscription_cancelledMerchant downgraded to free or cancelled
app/payment_succeededRecurring charge cleared
app/payment_failedRecurring charge declined — usually 3 retries before cancellation
app/usage_charge_createdPer-use charge applied (for usage-based billing)
See Billing for full setup.

5. Uninstall

Merchants can uninstall an app at any time from their admin. When they do:
  1. OAuth tokens are revoked immediately — your app cannot make any further API calls
  2. Extensions are unmounted — blocks/checkout/admin extensions stop rendering
  3. app/uninstalled webhook fires (this is the only webhook your app receives after uninstall)
  4. Webhook subscriptions are cleared

app/uninstalled webhook

{
  "topic": "app/uninstalled",
  "createdAt": "2026-05-06T12:00:00Z",
  "data": {
    "installationId": "inst_01H...",
    "merchantId": "mer_01H...",
    "uninstalledAt": "2026-05-06T12:00:00Z",
    "uninstallReason": "merchant_initiated"
  }
}
Use this to:
  • Clean up merchant data (or schedule it — see GDPR below)
  • Cancel any active subscriptions / usage charges
  • Send an “we’d love your feedback” email
  • Update internal analytics
app/uninstalled is delivered with a 3-retry exponential backoff (1m / 5m / 15m). If your endpoint is down for the full retry window, the webhook is dropped — but the uninstall still happens. Always reconcile with the installations list endpoint on a daily cron rather than relying on the webhook alone.

6. GDPR Lifecycle Webhooks

Three additional webhooks satisfy GDPR / CCPA obligations. Subscribing to these is mandatory for any app published to the public marketplace.

customers/data_request

Fires when a merchant or customer requests a data export.
{
  "topic": "customers/data_request",
  "data": {
    "shopDomain": "merchant.example.com",
    "customerId": 1234567,
    "customerEmail": "shopper@example.com",
    "ordersRequested": [9876, 9877]
  }
}
Within 30 days, you must compile every piece of customer data your app stores and email it to the merchant (the merchant then forwards to the customer).

customers/redact

Fires when a customer exercises their right to be forgotten, 48 hours after the merchant approves the request.
{
  "topic": "customers/redact",
  "data": {
    "shopDomain": "merchant.example.com",
    "customerId": 1234567,
    "customerEmail": "shopper@example.com",
    "ordersToRedact": [9876, 9877]
  }
}
You must permanently delete or anonymize all data tied to this customer within 30 days of receiving this webhook. See GDPR Reference for the audit trail format.

shop/redact

Fires 48 hours after an app is uninstalled (i.e. 48h after app/uninstalled).
{
  "topic": "shop/redact",
  "data": {
    "shopDomain": "merchant.example.com",
    "shopId": 12345,
    "uninstalledAt": "2026-05-04T12:00:00Z"
  }
}
You must permanently delete all merchant-tied data within 30 days. The 48-hour delay gives merchants a window to reinstall if they uninstalled by accident — during that window, your data should remain intact.

Webhook Delivery Guarantees

  • Signature: HMAC-SHA256 of the raw request body, signed with your clientSecret, sent in the X-LMS-Hmac-Sha256 header. Always verify before processing.
  • Retries: 3 attempts with exponential backoff (1m, 5m, 15m). After the 3rd failure, the event is dropped.
  • Ordering: Best-effort, not guaranteed. Use createdAt to sequence; use idempotency keys (e.g. installationId + topic + createdAt) to dedupe.
  • At-least-once: A successful response (2xx) acks the event. Missing the ack causes a retry — your handler must be idempotent.
See Webhook Verification for the HMAC signature code.

Testing Webhooks Locally

Waiting for a real merchant install, order, or GDPR request just to verify a handler works is painful — and some events (like customers/redact) are nearly impossible to reproduce on demand. The lms webhook trigger CLI command generates a sample payload for any topic, signs it with your app’s clientSecret, and POSTs it to a local URL so you can exercise your handler end-to-end.

Install the CLI

The command ships with the LaunchMyStore CLI. If you haven’t set it up yet, see CLI Setup. Once configured, your clientSecret is read from LMS_CLIENT_SECRET or .lmsrc.json, so most invocations need only the topic.

Common scenarios

# Fire a sample order at your local handler
lms webhook trigger orders/create

# Override the URL if your handler isn't on the default port
lms webhook trigger orders/create --url http://localhost:4000/api/webhooks

Headers it sends

Each request mirrors what production webhook delivery sends, so the same verification code path runs in dev:
HeaderValue
Content-Typeapplication/json
X-LMS-TopicThe topic you passed (e.g. orders/create)
X-LMS-Webhook-IdRandom UUID — useful for dedupe testing
X-LMS-Delivery-Attempt1 (bump manually if you want to test retry behavior)
X-LMS-Hmac-SHA256Base64 HMAC-SHA256 of the raw body, signed with your clientSecret
Your handler should verify X-LMS-Hmac-SHA256 against the raw request body before doing anything else. See Webhook Verification for the exact comparison code.
Pair lms webhook trigger with a local tunnel (e.g. ngrok) to test handlers that need to call back into the LaunchMyStore API. The signature is computed against your real clientSecret, so production-grade verification works either way.

Lifecycle Checklist

Before you publish your app to the marketplace, your handler must:
1

Handle OAuth callback (`/oauth/callback`)

Exchange code for tokens, create your installation row, redirect merchant to your dashboard.
2

Handle `app/installed` webhook

Idempotent provisioning — running it twice should not duplicate data.
3

Refresh tokens before they expire

Background job or just-in-time refresh inside your API client.
4

Handle `app/scopes_update`

Detect new scopes, prompt merchant to re-authorize.
5

Handle `app/uninstalled`

Mark installation as inactive, schedule data retention timer.
6

Handle `customers/data_request`, `customers/redact`, `shop/redact`

Required for marketplace approval — compile/delete data within 30 days.