---
title: "Product Published"
method: POST
path: "product.published"
tags: ["Products"]
---

# Product Published

`POST product.published` (webhook)

Sent when a product is published

Required permissions:
 - `access_pass:basic:read`
 - `webhook_receive:products`

## Headers

- `webhook-id` string, required — A unique identifier for this webhook request
- `webhook-signature` string, required — The signature of the webhook request with the webhook version prepended
- `webhook-timestamp` string, required — The timestamp in seconds since the Unix epoch that the webhook was sent at on the server

## Payload

- object
  - `account_id` string, nullable — The account ID that this webhook event is associated with
  - `api_version` 'v1', required — The API version for this webhook
  - `api_version_date` string, nullable, required — The dated API version (Api-Version-Date) the payload is serialized to
  - `data` Product, required
    - `account` object, nullable, required — Account that sells this product.
    - `average_review_rating` number, required — Average star rating across published reviews for this product, from `1.0` to `5.0`. Returns `0.0` when no published-review rating is available.
    - `created_at` string, required — When the product was created, as an ISO 8601 timestamp.
    - `custom_cta` 'get_access' | 'join' | 'order_now' | 'shop_now' | 'call_now' | 'donate_now' | 'contact_us' | 'sign_up' | 'subscribe' | 'purchase' | 'get_offer' | 'apply_now' | 'complete_order' | 'null', nullable, required — Call-to-action button label shown on the product purchase page.
    - `custom_cta_url` string, nullable, required — URL the call-to-action button links to instead of checkout.
    - `custom_statement_descriptor` string, nullable, required — Custom text label on customer's bank statement.
    - `default_plan` ProductPublicPlan, required
      - `billing_period` number, nullable, required — Number of days between recurring charges, such as 30 for monthly or 365 for annual. `null` for one-time plans.
      - `expiration_days` number, nullable, required — Access duration in days for expiration-based plans. `null` for plans without an expiration.
      - `id` string, required — Plan ID, prefixed `plan_`.
      - `initial_price` Money, required
        - `amount` string, required — The amount in major units, as an exact decimal string — `"10.00"` is ten dollars. A string so no float rounds it in transit.
        - `currency` string, required — Three-letter ISO 4217 currency code, lowercase.
        - `decimals` integer, required — How many decimal places the amount CARRIES — the precision the charge itself runs at.
        - `display_decimals` integer, required — How many decimal places to SHOW. Usually equal to `decimals`, and deliberately not always: COP is charged in centavos but written in whole pesos, so it is `2` and `0`. Format the number in your own locale using this.
      - `plan_type` 'renewal' | 'one_time', required — Billing model for this plan: `one_time` or `renewal`.
      - `renewal_price` Money, required
        - `amount` string, required — The amount in major units, as an exact decimal string — `"10.00"` is ten dollars. A string so no float rounds it in transit.
        - `currency` string, required — Three-letter ISO 4217 currency code, lowercase.
        - `decimals` integer, required — How many decimal places the amount CARRIES — the precision the charge itself runs at.
        - `display_decimals` integer, required — How many decimal places to SHOW. Usually equal to `decimals`, and deliberately not always: COP is charged in centavos but written in whole pesos, so it is `2` and `0`. Format the number in your own locale using this.
      - `title` string, nullable, required — Plan display name shown to customers. `null` if no title has been set.
      - `unlimited_stock` boolean, required — Whether the plan has unlimited stock.
      - `visibility` 'visible' | 'hidden' | 'archived' | 'quick_link', required — Where this plan can be seen. `visible` plans appear on the product page.
    - `description` string, nullable, required — Written description displayed on the product page. `null` if none is set.
    - `external_identifier` string, nullable, required — External identifier stored on the product for your own reference.
    - `gallery_images` ProductGalleryImage[], required
      - `content_type` string, nullable, required — Uploaded file MIME type, such as image/jpeg.
      - `id` string, required — Gallery image ID.
      - `url` string, nullable, required — Pre-optimized URL for rendering this image on the client.
    - `global_affiliate_percentage` number, nullable, required — Commission rate affiliates earn through the global affiliate program.
    - `global_affiliate_status` 'enabled' | 'disabled' | 'null', nullable, required — Enrollment status in the global affiliate program.
    - `headline` string, nullable, required — Short marketing headline displayed on product page.
    - `id` string, required — Product ID, prefixed `prod_`.
    - `labels` string[], required
    - `marketplace_status` 'not_available' | 'pending_review' | 'live_marketplace', required — Listing state on the whop.com marketplace. `pending_review` means submitted and awaiting review; `live_marketplace` means approved and discoverable.
    - `member_affiliate_percentage` number, nullable, required — Commission rate members earn through the member affiliate program.
    - `member_affiliate_status` 'enabled' | 'disabled' | 'null', nullable, required — Enrollment status in the member affiliate program.
    - `member_count` number, required — Active memberships for this product; 0 if public member counts are disabled.
    - `metadata` object, nullable, required — Custom key-value pairs stored on the product.
    - `owner_user` object, nullable, required — User who owns the account selling this product.
    - `product_tax_code` object, nullable, required — Tax classification code for this product, or `null` if no tax code is set.
    - `published_reviews_count` number, required — Published customer reviews for this product.
    - `route` string, required — URL slug for the product's public link.
    - `title` string, required — Product display name shown to customers.
    - `updated_at` string, required — When the product was last updated, as an ISO 8601 timestamp.
    - `variant_attributes` object, nullable, required — The option set the product's variants span, as a map of attribute name to the values in use, e.g. `{"color": ["Blue", "Red"], "size": ["S", "M", "L"]}`. Derived from the visible, non-invoice plans that carry `attributes`: keys alphabetical, values in the order the plans were created. Read-only. `null` when the product has no variants.
    - `variants` PlanListItem[], nullable, required
      - `account` AccountSummary, required
        - `id` string, required — Account ID, prefixed `biz_`.
        - `title` string, required — Account display name.
      - `adaptive_pricing_enabled` boolean, required — Whether adaptive pricing is enabled for this plan. Raw setting — does not check processor compatibility or feature flags.
      - `attributes` object, nullable, required — Attribute values that make this plan one variant of its product, as a map of attribute name to value, e.g. `{"color": "Blue", "size": "Large"}`. Names are snake_case identifiers and come back in alphabetical order. Every variant plan on a product carries the same attribute names and a distinct set of values; the product lists the full option set as `variant_attributes`. `null` for a plan that is not a variant.
      - `billing_period` number, nullable, required — Number of days between recurring charges, such as 30 for monthly or 365 for annual. `null` for one-time plans.
      - `cancel_discount_intervals` number, nullable, required — Billing intervals the cancellation discount applies to (`0` forever, `1` first payment, or a month count). `null` when none is offered or the actor lacks the `plan:basic:read` scope.
      - `cancel_discount_percentage` number, nullable, required — Cancellation discount as a whole-number percentage. `null` when none is offered or the actor lacks the `plan:basic:read` scope.
      - `checkout_styling` object, nullable, required — Plan-level checkout styling (`background_color`, `button_color`, `font_family`, `border_style`); `null` inherits the account default.
      - `created_at` string, required — When the plan was created, as an ISO 8601 timestamp.
      - `currency` string, required — Three-letter ISO currency code for this plan's prices.
      - `custom_fields` PlanCustomField[], required
        - `field_type` 'text', required — Custom field input type.
        - `id` string, required — Custom field ID, prefixed `field_`.
        - `name` string, required — Field label shown to customer at checkout.
        - `order` number, required — Field position on checkout form.
        - `placeholder` string, nullable, required — Placeholder text shown in the empty field. `null` if none is set.
        - `required` boolean, required — Whether the customer must complete this field to check out.
      - `description` string, nullable, required — Customer-visible plan description. Maximum 1000 characters. `null` if no description is set.
      - `expiration_days` number, nullable, required — Access duration in days for expiration-based plans, such as 365 for a one-year pass. `null` for plans without an expiration.
      - `formatted_price` string, required — Human-readable price for display (currency + interval), e.g. "$10 / month".
      - `id` string, required — Plan ID, prefixed `plan_`.
      - `image` object, nullable, required — Pricing-tier image (`url`, `blurhash`) shown on the product page; `null` when no image is set.
      - `initial_price` number, required — Initial purchase price in plan currency.
      - `initial_price_due` Money, required
        - `amount` string, required — The amount in major units, as an exact decimal string — `"10.00"` is ten dollars. A string so no float rounds it in transit.
        - `currency` string, required — Three-letter ISO 4217 currency code, lowercase.
        - `decimals` integer, required — How many decimal places the amount CARRIES — the precision the charge itself runs at.
        - `display_decimals` integer, required — How many decimal places to SHOW. Usually equal to `decimals`, and deliberately not always: COP is charged in centavos but written in whole pesos, so it is `2` and `0`. Format the number in your own locale using this.
      - `internal_notes` string, nullable, required — Private notes not shown to customers. `null` unless the actor has the `plan:basic:read` scope on the plan's account.
      - `invoice` object, nullable, required — Invoice this plan was generated for; `null` unless created for an invoice.
      - `member_count` number, nullable, required — Active memberships through this plan. `null` unless the actor has the `plan:basic:read` scope on the plan's account.
      - `metadata` object, nullable, required — Custom key-value pairs stored on the plan. Included in webhook payloads for payment and membership events. Maximum 50 keys, 100 characters per key, 500 characters per value. The reserved keys `custom_cta` and `custom_cta_url`, when set, override the product's checkout call to action for this plan.
      - `offer_cancel_discount` boolean, nullable, required — Whether a cancellation discount is offered. `null` unless the actor has the `plan:basic:read` scope on the plan's account.
      - `payment_method_configuration` object, nullable, required — Payment method configuration (`enabled`, `disabled`, `include_platform_defaults`); `null` when plan uses default settings.
      - `plan_type` 'renewal' | 'one_time', required — Billing model for this plan.
      - `product` object, nullable, required — Product this plan belongs to; `null` for standalone plans.
      - `purchase_url` string, required — URL where customers can purchase this plan directly.
      - `release_method` 'buy_now' | 'waitlist', required — Sales method for this plan.
      - `renewal_price` number, required — Recurring price charged every billing period.
      - `sku` string, nullable, required — Stock keeping unit, free text set by the seller (e.g. `TSHIRT-LARGE-BLUE`). Not enforced unique. `null` when unset.
      - `split_pay_required_payments` number, nullable, required — Installment payments required before the subscription pauses. Must be greater than 1. `null` if split pay is not configured.
      - `stock` number, nullable, required — Units available for purchase. `null` unless the actor has the `plan:basic:read` scope on the plan's account.
      - `strike_through_initial_price` number, nullable, required — Original initial price shown with a strikethrough, in the plan's currency. `null` when no strikethrough is set.
      - `strike_through_renewal_price` number, nullable, required — Original renewal price shown with a strikethrough, in the plan's currency. `null` when no strikethrough is set.
      - `three_ds_level` 'mandate_challenge' | 'mandate_if_required' | 'frictionless_if_required' | 'null', nullable, required — 3D Secure behavior for supported on-session card payments. `mandate_challenge` requires a 3DS challenge before payment processing; `mandate_if_required` mandates a challenge only when the payment processor requires it; `frictionless_if_required` uses the regular frictionless 3DS flow. Payments of $1,000 or more use `mandate_if_required` unless `mandate_challenge` is selected. Risk and authentication recovery requirements can override the preference. `null` inherits the account default.
      - `title` string, nullable, required — Plan display name shown to customers. Maximum 30 characters. A variant created without one defaults to its attribute values joined with ` / `. `null` if no title has been set.
      - `trial_period_days` number, nullable, required — Free trial days before the first renewal charge. `null` if no trial is configured or the user has already used a trial for this plan.
      - `unlimited_stock` boolean, required — Whether the plan has unlimited stock. When `true`, the `stock` field is ignored; waitlist plans always report `true`.
      - `updated_at` string, required — When the plan was last updated, as an ISO 8601 timestamp.
      - `visibility` 'visible' | 'hidden' | 'archived' | 'quick_link', required — Controls where this plan can be seen. When `hidden`, the plan is reachable only by its direct link.
    - `verified` boolean, required — Whether the product has been verified by Whop.
    - `visibility` string, nullable, required — Whether the product is publicly visible, hidden, or archived.
  - `id` string, required — A unique ID for every single webhook request
  - `previous_attributes` object — For some `.updated` events, the old values of the payload fields that changed, keyed by field name. Omitted when no capture is available for the event
  - `timestamp` string, date-time, required — The timestamp in ISO 8601 format that the webhook was sent at on the server
  - `type` 'product.published', required — The webhook event type

## Acknowledgement `200`

Return a 200 status to indicate that the data was received successfully

---

[API](https://skmtc.dev/whop/apis/whop-api.md) · [All operations](https://skmtc.dev/whop/apis/whop-api/llms.txt) · [OpenAPI document](https://skmtc.dev/whop/apis/whop-api/revisions/78e1ed7366cd?raw)
