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

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

    ### 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",
        "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>

    #### 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",
        "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",
        "products": [
          {
            "uniqid": "prod_xyz",
            "title": "Pro License",
            "description": "Full access to all features",
            "price": 49.99,
            "price_display": 49.99,
            "currency": "USD",
            "type": "SERVICE"
          }
        ],
        "created_at": "2026-01-15T09:10:00.000Z",
        "updated_at": "2026-01-15T10:10:00.000Z"
      },
      "created_at": 1705318200
    }
    ```

    #### 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",
        "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>
