> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shoppex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook events

> Supported webhook event names and example payloads

## Event types

Shoppex supports order, subscription, product, query, feedback, affiliate, and reseller webhooks. Event names use colons as separators, like `order:paid`.

<Note>
  When creating webhooks through the Dev API, send the exact event name, for example `order:paid`. Wildcard patterns like `invoice.*` do not work. Fetch the full allowlist from `GET /dev/v1/webhooks/events`.
</Note>

<Note>
  These event payloads are not the same as the `dynamic_webhook` callback used by `DYNAMIC` products. For that contract, see [Dynamic product delivery](/developers/dynamic-delivery).
</Note>

***

## Supported event names

<Tabs>
  <Tab title="Order events">
    | Event                          | Description                           |
    | ------------------------------ | ------------------------------------- |
    | `order:created`                | Order created                         |
    | `order:updated`                | Order updated                         |
    | `order:partial`                | Partial payment received              |
    | `order:paid`                   | Order fully paid                      |
    | `order:cancelled`              | Order cancelled or expired            |
    | `order:disputed`               | Order dispute opened                  |
    | `order:created:product`        | Order created with product payload    |
    | `order:updated:product`        | Order updated with product payload    |
    | `order:partial:product`        | Partial payment with product payload  |
    | `order:paid:product`           | Paid order with product payload       |
    | `order:cancelled:product`      | Cancelled order with product payload  |
    | `order:disputed:product`       | Disputed order with product payload   |
    | `order:manual_payment_pending` | Manual payment review required        |
    | `order:item.delivered`         | Line item fulfilled with late content |
    | `order:item.delivery_failed`   | Dynamic line item delivery failed     |

    <Note>
      For paid product purchases, subscribe to `order:paid` or `order:paid:product`.
      If that purchase reduces available stock, Shoppex does not send a separate `product:stock` event for the same checkout.
    </Note>

    <Note>
      `order:item.delivered` and `order:item.delivery_failed` report per-line-item fulfillment after payment. See [Asynchronous and Manual Fulfillment](/developers/fulfillment#fulfillment-webhook-events) for their payloads and recovery flow.
    </Note>

    ### Payload families

    #### Invoice payload

    These events use the same base invoice payload shape as `order:paid`:

    * `order:created`
    * `order:updated`
    * `order:partial`
    * `order:paid`
    * `order:cancelled`
    * `order:disputed`
    * `order:manual_payment_pending`

    Each invoice payload includes `line_items`. Every entry includes the stable line-item
    `id`, product snapshot, optional variant, quantity, `delivery_status`, and
    `delivered_at`. Paid events also include `license_keys` on delivered `SERIALS`
    line items. Other lifecycle events omit the keys.

    #### Invoice payload with products

    These events use the same invoice payload plus the `products` array, like `order:paid:product`:

    * `order:created:product`
    * `order:updated:product`
    * `order:partial:product`
    * `order:paid:product`
    * `order:cancelled:product`
    * `order:disputed:product`

    A shop-level subscription receives the complete order. A webhook configured on
    a specific product receives only that product's matching `line_items` and
    `products` entries. In a cart containing multiple serial products, product A's
    endpoint therefore never receives product B's license keys.

    ### Example payloads

    #### order:paid

    Triggered when an order/invoice is successfully paid.

    ```json theme={"system"}
    {
      "event": "order:paid",
      "data": {
        "uniqid": "abc123def456",
        "type": "PRODUCT",
        "status": "COMPLETED",
        "gateway": "STRIPE",
        "total": 49.99,
        "total_display": 49.99,
        "currency": "USD",
        "exchange_rate": 1,
        "crypto_exchange_rate": 0,
        "crypto_gateway": null,
        "apm_method": "CARD",
        "customer_email": "customer@example.com",
        "country": "US",
        "quantity": 1,
        "product_id": "prod_xyz",
        "product_title": "Pro License",
        "line_items": [
          {
            "id": "018f6f2e-7c3a-7b21-9d4e-5a1b2c3d4e5f",
            "product_id": "prod_xyz",
            "product_title": "Pro License",
            "product_type": "SERIALS",
            "variant_id": "variant_pro",
            "variant_title": "Pro",
            "quantity": 1,
            "delivery_status": "DELIVERED",
            "delivered_at": "2026-01-15T10:10:00.000Z",
            "license_keys": [
              { "key": "AAAA-BBBB-CCCC" }
            ]
          }
        ],
        "is_developer_invoice": false,
        "created_at": "2026-01-15T09:10:00.000Z",
        "updated_at": "2026-01-15T10:10:00.000Z"
      },
      "created_at": 1705318200
    }
    ```

    <Note>
      Dashboard simulation for `order:paid` uses this same envelope shape and the same core fields, just with synthetic values.
    </Note>

    <Warning>
      `license_keys` contains the unredacted product keys delivered to the buyer. Treat
      the webhook body as sensitive, avoid recording it in application logs, and only
      forward it to systems that are allowed to store the delivered product.
    </Warning>

    #### order:cancelled

    Triggered when an order is cancelled or expires.

    ```json theme={"system"}
    {
      "event": "order:cancelled",
      "data": {
        "uniqid": "abc123def456",
        "type": "PRODUCT",
        "status": "VOIDED",
        "gateway": null,
        "total": 49.99,
        "total_display": 49.99,
        "currency": "USD",
        "exchange_rate": 1,
        "crypto_exchange_rate": 0,
        "crypto_gateway": null,
        "apm_method": null,
        "customer_email": "customer@example.com",
        "product_id": "prod_xyz",
        "product_title": "Pro License",
        "line_items": [
          {
            "id": "018f6f2e-7c3a-7b21-9d4e-5a1b2c3d4e5f",
            "product_id": "prod_xyz",
            "product_title": "Pro License",
            "product_type": "SERIALS",
            "variant_id": "variant_pro",
            "variant_title": "Pro",
            "quantity": 1,
            "delivery_status": "PENDING",
            "delivered_at": null
          }
        ],
        "created_at": "2026-01-15T09:10:00.000Z",
        "updated_at": "2026-01-16T09:03:20.000Z"
      },
      "created_at": 1705400600
    }
    ```

    #### order:paid:product

    Same as `order:paid` but includes full product details in the payload.

    ```json theme={"system"}
    {
      "event": "order:paid:product",
      "data": {
        "uniqid": "abc123def456",
        "type": "PRODUCT",
        "status": "COMPLETED",
        "gateway": "STRIPE",
        "total": 49.99,
        "total_display": 49.99,
        "currency": "USD",
        "exchange_rate": 1,
        "crypto_exchange_rate": 0,
        "crypto_gateway": null,
        "apm_method": "CARD",
        "customer_email": "customer@example.com",
        "product_id": "prod_xyz",
        "product_title": "Pro License",
        "line_items": [
          {
            "id": "018f6f2e-7c3a-7b21-9d4e-5a1b2c3d4e5f",
            "product_id": "prod_xyz",
            "product_title": "Pro License",
            "product_type": "SERIALS",
            "variant_id": "variant_pro",
            "variant_title": "Pro",
            "quantity": 1,
            "delivery_status": "DELIVERED",
            "delivered_at": "2026-01-15T10:10:00.000Z",
            "license_keys": [
              { "key": "AAAA-BBBB-CCCC" }
            ]
          }
        ],
        "products": [
          {
            "uniqid": "prod_xyz",
            "title": "Pro License",
            "description": "Full access to all features",
            "price": 49.99,
            "price_display": 49.99,
            "currency": "USD",
            "type": "SERIALS"
          }
        ],
        "created_at": "2026-01-15T09:10:00.000Z",
        "updated_at": "2026-01-15T10:10:00.000Z"
      },
      "created_at": 1705318200
    }
    ```

    `variant_id` and `variant_title` are `null` when the product has no variant.
    `license_keys` is omitted unless a paid `SERIALS` line has committed keys. Use
    the line-item `id` as the stable correlation key; carts can contain the same
    product more than once with different variants.

    #### order:cancelled:product

    Same as `order:cancelled` but includes full product details.

    #### order:manual\_payment\_pending

    Triggered when Shoppex is waiting for an offline or manual payment review.

    ```json theme={"system"}
    {
      "event": "order:manual_payment_pending",
      "data": {
        "uniqid": "abc123def456",
        "type": "PRODUCT",
        "status": "PENDING",
        "gateway": "MANUAL",
        "total": 49.99,
        "total_display": 49.99,
        "currency": "USD",
        "exchange_rate": 1,
        "crypto_exchange_rate": 0,
        "crypto_gateway": null,
        "apm_method": null,
        "customer_email": "customer@example.com",
        "country": "US",
        "quantity": 1,
        "product_id": "prod_xyz",
        "product_title": "Pro License",
        "line_items": [
          {
            "id": "018f6f2e-7c3a-7b21-9d4e-5a1b2c3d4e5f",
            "product_id": "prod_xyz",
            "product_title": "Pro License",
            "product_type": "SERIALS",
            "variant_id": "variant_pro",
            "variant_title": "Pro",
            "quantity": 1,
            "delivery_status": "PENDING",
            "delivered_at": null
          }
        ],
        "is_developer_invoice": false,
        "created_at": "2026-01-15T09:10:00.000Z",
        "updated_at": "2026-01-15T09:20:00.000Z"
      },
      "created_at": 1705315200
    }
    ```
  </Tab>

  <Tab title="Subscription events">
    | Event                                | Description                                 |
    | ------------------------------------ | ------------------------------------------- |
    | `subscription:trial:started`         | Trial started                               |
    | `subscription:trial:ended`           | Trial ended                                 |
    | `subscription:created`               | Subscription created                        |
    | `subscription:updated`               | Subscription updated                        |
    | `subscription:renewed`               | Subscription renewed                        |
    | `subscription:cancelled`             | Subscription cancelled                      |
    | `subscription:upcoming`              | Upcoming renewal reminder                   |
    | `subscription:trial:started:product` | Trial started with product payload          |
    | `subscription:trial:ended:product`   | Trial ended with product payload            |
    | `subscription:created:product`       | Subscription created with product payload   |
    | `subscription:updated:product`       | Subscription updated with product payload   |
    | `subscription:renewed:product`       | Subscription renewed with product payload   |
    | `subscription:cancelled:product`     | Subscription cancelled with product payload |
    | `subscription:upcoming:product`      | Upcoming renewal with product payload       |

    ### Payload family

    These events use the same subscription payload shape as `subscription:created`:

    * `subscription:trial:started`
    * `subscription:trial:ended`
    * `subscription:created`
    * `subscription:updated`
    * `subscription:renewed`
    * `subscription:cancelled`
    * `subscription:upcoming`
    * `subscription:trial:started:product`
    * `subscription:trial:ended:product`
    * `subscription:created:product`
    * `subscription:updated:product`
    * `subscription:renewed:product`
    * `subscription:cancelled:product`
    * `subscription:upcoming:product`

    ### Example payloads

    #### subscription:created

    Triggered when a new subscription starts.

    ```json theme={"system"}
    {
      "event": "subscription:created",
      "data": {
        "id": "sub_abc123",
        "status": "ACTIVE",
        "gateway": "STRIPE",
        "customer_id": "cust_xyz789",
        "current_period_start": "2026-01-15T00:00:00.000Z",
        "current_period_end": "2026-02-15T00:00:00.000Z",
        "created_at": "2026-01-15T00:00:00.000Z"
      },
      "created_at": 1705314600
    }
    ```

    #### subscription:cancelled

    Triggered when a subscription is cancelled.

    ```json theme={"system"}
    {
      "event": "subscription:cancelled",
      "data": {
        "id": "sub_abc123",
        "status": "CANCELLED",
        "gateway": "STRIPE",
        "customer_id": "cust_xyz789",
        "current_period_start": "2026-01-15T00:00:00.000Z",
        "current_period_end": "2026-02-15T00:00:00.000Z",
        "created_at": "2026-01-15T00:00:00.000Z"
      },
      "created_at": 1705918200
    }
    ```

    #### subscription:renewed

    Triggered when a subscription renews for a new billing period.

    ```json theme={"system"}
    {
      "event": "subscription:renewed",
      "data": {
        "id": "sub_abc123",
        "status": "ACTIVE",
        "gateway": "STRIPE",
        "customer_id": "cust_xyz789",
        "current_period_start": "2026-02-15T00:00:00.000Z",
        "current_period_end": "2026-03-15T00:00:00.000Z",
        "created_at": "2026-01-15T00:00:00.000Z"
      },
      "created_at": 1707991800
    }
    ```
  </Tab>

  <Tab title="Other events">
    | Event                               | Description                                                         |
    | ----------------------------------- | ------------------------------------------------------------------- |
    | `product:created`                   | Product created                                                     |
    | `product:edited`                    | Product updated                                                     |
    | `product:stock`                     | Product stock changed through a direct catalog/product update       |
    | `product:dynamic`                   | Dynamic product webhook dispatched                                  |
    | `query:created`                     | Support query created                                               |
    | `query:replied`                     | Support query replied                                               |
    | `feedback:received`                 | Feedback received                                                   |
    | `affiliate:payout_requested`        | Affiliate payout requested                                          |
    | `reseller:enrolled`                 | Customer became an active reseller                                  |
    | `reseller:application.created`      | Customer applied to the reseller program                            |
    | `reseller:application.rejected`     | Reseller application rejected                                       |
    | `reseller:suspended`                | Reseller suspended                                                  |
    | `reseller:order.completed`          | Wholesale reseller order paid and delivered                         |
    | `reseller:tier.advanced`            | Reseller reached a higher discount tier automatically               |
    | `reseller:supplier_order.delivered` | A sold item was bought automatically at your supplier and delivered |
    | `reseller:supplier_order.failed`    | An automatic supplier purchase failed and the customer was refunded |

    ### Payload notes

    #### Invoice-context product payload

    `product:dynamic` currently uses the same invoice-style payload family as the order events.
    It is not the same as the direct `dynamic_webhook` delivery callback.

    #### Runtime-specific events

    `product:stock` is for direct catalog stock changes, such as dashboard or API product edits.
    For stock consumed by checkout, use the matching order event, usually `order:paid:product`.

    #### Reseller events

    Four enrollment-lifecycle events share one payload family: a single `reseller` object. They are
    `reseller:enrolled`, `reseller:application.created`, `reseller:application.rejected`, and
    `reseller:suspended`. `reseller:order.completed`, `reseller:tier.advanced`, and the two
    `reseller:supplier_order.*` events each have their own shape, documented below.

    `reseller:enrolled` fires on all three enrollment paths (invite accepted, self-enrollment in
    open mode, and merchant approval of an application). Reinstating a suspended reseller does not
    send an event. There is no `reseller:reinstated`.

    The two `reseller:supplier_order.*` events belong to the **reselling** shop, not the supplier's:
    they tell you what happened when your own store auto-purchased a sold item from your supplier.

    The remaining supported event names on this page are listed as valid event values.
    Dedicated public example payloads for `product:created`, `product:edited`, `product:stock`, `query:created`, `query:replied`, `feedback:received`, and `affiliate:payout_requested` are not expanded here yet.

    ### Example payload

    #### product:dynamic

    Triggered after Shoppex starts dynamic product delivery for an invoice-backed purchase.
    This webhook uses invoice context and is separate from the direct `dynamic_webhook` delivery request.

    ```json theme={"system"}
    {
      "event": "product:dynamic",
      "data": {
        "uniqid": "abc123def456",
        "type": "PRODUCT",
        "status": "COMPLETED",
        "gateway": "STRIPE",
        "total": 49.99,
        "total_display": 49.99,
        "currency": "USD",
        "customer_email": "customer@example.com",
        "country": "US",
        "quantity": 1,
        "product_id": "prod_xyz",
        "product_title": "Dynamic Role",
        "is_developer_invoice": false,
        "created_at": "2026-01-15T09:10:00.000Z",
        "updated_at": "2026-01-15T10:10:00.000Z"
      },
      "created_at": 1705318200
    }
    ```

    #### reseller:enrolled

    Triggered when a customer becomes an active reseller. `reseller:application.created`,
    `reseller:application.rejected` and `reseller:suspended` carry the identical `reseller` object —
    only `status` and the timestamp fields differ.

    ```json theme={"system"}
    {
      "event": "reseller:enrolled",
      "data": {
        "reseller": {
          "id": "019cb402-1111-7000-8000-000000000001",
          "customer_id": "019cb402-2222-7000-8000-000000000002",
          "email": "wholesale@example.com",
          "status": "ACTIVE",
          "tier": {
            "id": "019cb402-3333-7000-8000-000000000003",
            "name": "Gold",
            "discount_percent": 15
          },
          "application_note": null,
          "lifetime_revenue": "0.00",
          "lifetime_orders": 0
        }
      },
      "created_at": 1753430400
    }
    ```

    `tier` is `null` when no tier is assigned yet. `application_note` is only populated for
    application-mode enrollments. `lifetime_revenue` is a decimal string, `lifetime_orders` an
    integer.

    #### reseller:order.completed

    Triggered when a wholesale reseller order is paid from the reseller's balance and delivered.
    The `items` array is the price snapshot frozen at order time. It is not re-derived from the
    current tier or product prices.

    ```json theme={"system"}
    {
      "event": "reseller:order.completed",
      "data": {
        "reseller": {
          "id": "019cb402-1111-7000-8000-000000000001",
          "email": "wholesale@example.com",
          "tier": {
            "id": "019cb402-3333-7000-8000-000000000003",
            "name": "Gold",
            "discount_percent": 15
          }
        },
        "order": {
          "invoice_uniqid": "019cb402-4444-7000-8000-000000000004",
          "total": "425.00",
          "currency": "USD",
          "items": [
            {
              "product_id": "019cb402-5555-7000-8000-000000000005",
              "variant_id": null,
              "quantity": 5,
              "unit_price": "85.00",
              "line_total": "425.00"
            }
          ]
        }
      },
      "created_at": 1753430460
    }
    ```

    #### reseller:tier.advanced

    Triggered when an order completion pushes a reseller's lifetime totals past a tier's
    auto-advance threshold. This event only ever fires alongside `reseller:order.completed` —
    there is no other code path that advances a tier automatically. Note the flat shape: the
    reseller is identified by `reseller_id` and `email` at the top level, not by a nested
    `reseller` object.

    ```json theme={"system"}
    {
      "event": "reseller:tier.advanced",
      "data": {
        "reseller_id": "019cb402-1111-7000-8000-000000000001",
        "email": "wholesale@example.com",
        "previous_tier": {
          "id": "019cb402-6666-7000-8000-000000000006",
          "name": "Silver",
          "discount_percent": 10
        },
        "new_tier": {
          "id": "019cb402-3333-7000-8000-000000000003",
          "name": "Gold",
          "discount_percent": 15
        },
        "lifetime_revenue": "12500.00",
        "lifetime_orders": 42
      },
      "created_at": 1753430461
    }
    ```

    `previous_tier` is `null` when the reseller had no tier before. `discount_percent` is a number
    on this event, unlike the decimal strings used for money fields.

    #### reseller:supplier\_order.delivered

    Triggered when one of your products is fulfilled by an automatic wholesale purchase at your
    supplier and the codes reached your buyer. Fires on your own shop.

    ```json theme={"system"}
    {
      "event": "reseller:supplier_order.delivered",
      "data": {
        "supplier_order_id": "019cb402-7777-7000-8000-000000000007",
        "status": "DELIVERED",
        "quantity": 2,
        "delivered_quantity": 2,
        "shortfall_quantity": 0,
        "amount": "18.00",
        "currency": "USD",
        "failure_reason": null,
        "retail_invoice": {
          "uniqid": "019cb402-8888-7000-8000-000000000008",
          "line_item_id": "019cb402-9999-7000-8000-000000000009"
        },
        "supplier_shop": {
          "id": "019cb402-aaaa-7000-8000-00000000000a",
          "invoice_uniqid": "019cb402-bbbb-7000-8000-00000000000b"
        }
      },
      "created_at": 1753430470
    }
    ```

    `amount` is what was charged to your reseller balance at the supplier. A partial supplier
    delivery still sends this event, with `shortfall_quantity` above zero.

    #### reseller:supplier\_order.failed

    Triggered when the automatic supplier purchase fails to deliver. `status` is `REFUNDED` when
    the buyer was refunded and `FAILED` when no refund was possible. `amount` carries the refunded
    sum or `null`. `supplier_shop.invoice_uniqid` is always `null` here, because no usable supplier
    invoice exists.

    ```json theme={"system"}
    {
      "event": "reseller:supplier_order.failed",
      "data": {
        "supplier_order_id": "019cb402-cccc-7000-8000-00000000000c",
        "status": "REFUNDED",
        "quantity": 1,
        "delivered_quantity": 0,
        "shortfall_quantity": 1,
        "amount": "9.00",
        "currency": "USD",
        "failure_reason": "SUPPLIER_OUT_OF_STOCK: The supplier could not deliver this item.",
        "retail_invoice": {
          "uniqid": "019cb402-dddd-7000-8000-00000000000d",
          "line_item_id": "019cb402-eeee-7000-8000-00000000000e"
        },
        "supplier_shop": {
          "id": "019cb402-aaaa-7000-8000-00000000000a",
          "invoice_uniqid": null
        }
      },
      "created_at": 1753430480
    }
    ```

    `failure_reason` is a `CODE: message` string. Common codes are `SUPPLIER_OUT_OF_STOCK`,
    `ATTEMPTS_EXHAUSTED`, and the guard failures for a disabled source, an inactive reseller
    relationship, or a supply chain deeper than three shops.
  </Tab>
</Tabs>

***

## Common fields

All webhook payloads include these top-level fields:

| Field        | Type   | Description                               |
| ------------ | ------ | ----------------------------------------- |
| `event`      | string | Event type, for example `order:paid`      |
| `data`       | object | Event-specific payload                    |
| `created_at` | number | Unix timestamp when the event was created |

<Note>
  Top-level webhook `created_at` is a Unix timestamp.
  Nested timestamps inside `data`, like invoice `created_at` / `updated_at` or subscription period fields, are ISO 8601 strings when present.
</Note>

***

<CardGroup cols={2}>
  <Card title="Webhooks" icon="webhook" href="/developers/webhooks">
    Setup, verification, and response handling
  </Card>

  <Card title="Dynamic delivery" icon="truck-fast" href="/developers/dynamic-delivery">
    Deliver products in real time through the webhook response
  </Card>
</CardGroup>
