> ## 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.

# Webhooks Overview

> Real-time event notifications

# Webhooks

Webhooks allow your app to receive real-time notifications when events occur in a merchant's store. Instead of polling for changes, LaunchMyStore pushes events to your server as they happen.

## How Webhooks Work

```mermaid theme={null}
sequenceDiagram
    participant Store
    participant LaunchMyStore
    participant YourApp
    
    Store->>LaunchMyStore: Event occurs (e.g., order created)
    LaunchMyStore->>YourApp: POST webhook payload
    YourApp->>LaunchMyStore: 200 OK response
```

## Registering Webhooks

Register webhooks during app installation. `GET`, `POST`, and `DELETE`
on `/api/v1/webhooks.json` are all gated by the **`read_shop`** scope
(there is no dedicated webhook write scope):

```javascript theme={null}
const response = await fetch('https://api.launchmystore.io/api/v1/webhooks.json', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,   // requires read_shop scope
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    topic: 'orders/create',
    callbackUrl: 'https://my-app.com/webhooks/orders',
    format: 'json'
  })
});
```

## Webhook Topics

Exactly **23** topics are registerable via `POST /api/v1/webhooks.json`.
Any topic outside this list is rejected with **422**.

### Orders

| Topic              | Description             |
| ------------------ | ----------------------- |
| `orders/create`    | New order placed        |
| `orders/updated`   | Order modified          |
| `orders/cancelled` | Order cancelled         |
| `orders/paid`      | Order payment confirmed |
| `orders/fulfilled` | Order fully fulfilled   |
| `orders/delete`    | Order deleted           |

### Refunds

| Topic            | Description          |
| ---------------- | -------------------- |
| `refunds/create` | A refund was created |

### Products

| Topic             | Description         |
| ----------------- | ------------------- |
| `products/create` | New product created |
| `products/update` | Product modified    |
| `products/delete` | Product deleted     |

### Customers

| Topic              | Description             |
| ------------------ | ----------------------- |
| `customers/create` | New customer registered |
| `customers/update` | Customer data modified  |
| `customers/delete` | Customer deleted        |

### Inventory

| Topic                     | Description             |
| ------------------------- | ----------------------- |
| `inventory_levels/update` | Inventory level changed |

### Fulfillments

| Topic                 | Description               |
| --------------------- | ------------------------- |
| `fulfillments/create` | A fulfillment was created |
| `fulfillments/update` | A fulfillment was updated |

### Subscriptions

| Topic                          | Description                    |
| ------------------------------ | ------------------------------ |
| `subscriptions/create`         | Recurring subscription created |
| `subscriptions/renew`          | Subscription renewed           |
| `subscriptions/update`         | Subscription updated           |
| `subscriptions/cancelled`      | Subscription cancelled         |
| `subscriptions/payment_failed` | A subscription payment failed  |

### App

| Topic                      | Description                                                                                                       |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `app/uninstalled`          | App removed from store                                                                                            |
| `app_subscriptions/update` | Billing state of your app's install changed (payment failed / grace expired / recovered / cancellation scheduled) |

<Note>
  GDPR compliance topics (`customers/data_request`, `customers/redact`,
  `shop/redact`) are delivered by the platform but are **not** registerable
  through `webhooks.json` — see [GDPR Webhooks](/api-reference/webhooks/gdpr).
</Note>

See [Webhook Topics](/api-reference/webhooks/topics) for each topic's payload shape.

## Webhook Payload

The request body **is** the event resource itself — there is no envelope.
Order, product, customer, collection and refund events are delivered as
snake\_case objects (`line_items`, `financial_status`,
money fields are decimal strings). The topic, contract version and sending
store are in the request **headers**, not the body:

| Header                   | Example                              |
| ------------------------ | ------------------------------------ |
| `X-LMS-Topic`            | `orders/create`                      |
| `X-LMS-Api-Version`      | `2026-06`                            |
| `X-LMS-Shop-Domain`      | `merchant-store.launchmystore.io`    |
| `X-LMS-Webhook-Id`       | delivery id — use for idempotency    |
| `X-LMS-Delivery-Attempt` | `1` on first try, `2`/`3` on retries |
| `X-LMS-Hmac-SHA256`      | base64 signature                     |

See [Webhook Topics](/api-reference/webhooks/topics) for each topic's payload shape.

## Verifying Webhooks

Every signed webhook carries an `X-LMS-Hmac-SHA256` header — a base64
HMAC-SHA256 of the **raw request body**, keyed by this webhook's signing
secret. The secret depends on how the webhook was created: the
per-subscription `secret` for API-registered webhooks
(`POST /api/v1/webhooks.json`), your app's `clientSecret` for
manifest-declared and GDPR webhooks, or the per-webhook `secret` shown in
the merchant admin for store-level webhooks. Compute the HMAC over the raw
bytes, never a re-serialized JSON object, then compare with a timing-safe
comparison.

See [Webhook Verification](/api-reference/webhooks/verification) for the exact
algorithm and copy-paste examples in Node.js, Python, Ruby, PHP and Go.

## Delivery & Retries

* **Timeout**: Your endpoint must respond within 10 seconds
* **Response**: Return 2xx status code to acknowledge receipt
* **Retries**: Failed deliveries are retried 3 times with exponential backoff:
  * 1st retry: 1 minute
  * 2nd retry: 5 minutes
  * 3rd retry: 15 minutes
* **Dead letter**: After 3 failures, the webhook is logged and no more retries

## Best Practices

<AccordionGroup>
  <Accordion title="Respond quickly">
    Return 200 immediately, then process asynchronously. Long-running handlers cause timeouts.
  </Accordion>

  <Accordion title="Handle duplicates">
    Webhooks may be delivered more than once. Deduplicate on the `X-LMS-Webhook-Id` request header.
  </Accordion>

  <Accordion title="Verify signatures">
    Always verify HMAC signatures to ensure webhooks are authentic.
  </Accordion>

  <Accordion title="Use a public HTTPS URL">
    Webhook (and fulfillment-service) callback URLs must use `http`/`https`
    and be publicly reachable. Link-local / cloud-metadata addresses
    (`169.254.0.0/16`, e.g. `169.254.169.254`) and the unspecified address
    (`0.0.0.0`) are rejected in **every** environment; private, loopback and
    `*.local`/`*.internal` hosts (`localhost`, `127.0.0.1`, `10.x`, `192.168.x`,
    `172.16–31.x`) are rejected in **production** (allowed in local dev so you
    can test against `localhost`). Use HTTPS in production.
  </Accordion>
</AccordionGroup>

## Managing Webhooks

### List Webhooks

```bash theme={null}
GET /api/v1/webhooks.json
```

### Delete Webhook

```bash theme={null}
DELETE /api/v1/webhooks/{webhook_id}.json
```
