diff --git a/whop/README.md b/whop/README.md
new file mode 100644
index 0000000..7ff823c
--- /dev/null
+++ b/whop/README.md
@@ -0,0 +1,26 @@
+Connect a Whop business to help Captain look up customers, their memberships and payments, and the products and plans they bought.
+
+## Setup
+
+1. In the Whop dashboard, open the developer settings of the business you want to connect and create an **Account API key**.
+2. Give the key a custom, read-only permission set instead of the Admin role. The tools read members and their email addresses, memberships, payments, products, and plans (`member:basic:read`, `member:email:read`, `payment:basic:read`, `plan:basic:read`).
+3. Copy the key when Whop shows it and enter it when installing.
+
+## Tools
+
+- **Find Customer by Email:** Find a customer by email address. Returns the Whop user ID the other tools need, plus member status, purchase count, and lifetime value in USD.
+- **List Memberships:** Check a customer's 10 most recent memberships, including canceled and expired ones, with status, renewal date, and product and plan IDs.
+- **Get Membership:** Check one membership's status, billing period, cancellation date and reason, and the link where the customer manages billing.
+- **List Payments:** Check a customer's 10 most recent payments, with amount, status, failure reason and decline code, refunds, and what was bought.
+- **Get Payment:** Check one payment in full, with totals, tax, payment method type, failure and retry details, refund state, and dispute alerts.
+- **Get Product:** Check a product's name, headline, description, and visibility.
+- **Get Plan:** Check a plan's initial and renewal price, billing period, free trial, and access length.
+
+Start with Find Customer by Email, then use the user ID with List Memberships and List Payments. Memberships return product and plan IDs; use Get Product and Get Plan to turn them into names and prices.
+
+## Notes
+
+- Use an Account API key for your own business. App API keys and OAuth tokens are for apps installed on other businesses and are not needed here.
+- Customers who paid as a guest or as another business may have no Whop user ID, so their memberships and payments cannot be listed by user.
+- License keys, payment client secrets, and payment recovery links are never included in what Captain sees.
+- The tools call Whop's current `api/v1` endpoints without pinning an `Api-Version-Date`, so responses follow Whop's latest API version.
diff --git a/whop/toolset.yml b/whop/toolset.yml
new file mode 100644
index 0000000..fdd8ad1
--- /dev/null
+++ b/whop/toolset.yml
@@ -0,0 +1,172 @@
+version: 0.0.1
+kind: captain_toolset
+name: Whop
+author: chatwoot
+category: Commerce
+description: Look up customers, memberships, payments, products, and pricing in a Whop business.
+
+secrets:
+ api_key:
+ label: Whop account API key
+ type: password
+ required: true
+
+tools:
+ - id: find_customer_by_email
+ title: Find Customer by Email
+ description: Find a customer of the Whop business by email address. Use this first to get the Whop user ID (starting with user_) that List Memberships and List Payments need. Also shows purchase count, lifetime spend, and member status.
+ http_method: GET
+ endpoint_url: https://api.whop.com/api/v1/people?email={{ email | strip | url_encode }}&first=5
+ auth_type: bearer
+ auth_config:
+ token: ${{ secrets.api_key }}
+ param_schema:
+ - name: email
+ type: string
+ description: The customer's email address.
+ required: true
+ response_template: |-
+ {% if response.data %}{% for person in response.data limit: 5 %}- {{ person.name | default: person.user.name | default: "No name" }} <{{ person.email | default: "no email" }}>
+ User ID: {% if person.user.id %}{{ person.user.id }}{% if person.user.username %} (@{{ person.user.username }}){% endif %}{% else %}none, this person has no Whop account linked{% endif %}
+ Member status: {{ person.member.status | default: "not a member" }}{% if person.member.joined_at %} · joined {{ person.member.joined_at | slice: 0, 10 }}{% endif %}
+ Purchases: {{ person.purchase_count | default: 0 }} · lifetime value: {{ person.ltv | default: 0 }} USD{% if person.last_purchase_at %} · last purchase {{ person.last_purchase_at | slice: 0, 10 }}{% endif %}
+ First seen: {{ person.first_seen_at | slice: 0, 10 }} · last seen: {{ person.last_seen_at | slice: 0, 10 }}{% if person.location.country %} · country: {{ person.location.country }}{% endif %}
+ {% else %}No Whop customer found with this email address.{% endfor %}{% else %}Whop returned an unexpected response.{% endif %}
+
+ - id: list_memberships
+ title: List Memberships
+ description: List a customer's 10 most recent Whop memberships, including canceled and expired ones, with status, renewal date, and product and plan IDs. Use the user ID from Find Customer by Email. Call this when a customer asks about access, renewal, or cancellation.
+ http_method: GET
+ endpoint_url: https://api.whop.com/api/v1/memberships?user_id={{ user_id | strip | url_encode }}&first=10&order=created_at&direction=desc
+ auth_type: bearer
+ auth_config:
+ token: ${{ secrets.api_key }}
+ param_schema:
+ - name: user_id
+ type: string
+ description: The Whop user ID from Find Customer by Email, starting with user_.
+ required: true
+ response_template: |-
+ {% if response.data %}{% for membership in response.data limit: 10 %}- Membership {{ membership.id }} · {{ membership.status | replace: "_", " " }}{% if membership.cancel_at_period_end %} · set to cancel at period end{% endif %}
+ Product ID: {{ membership.product_id }} · plan ID: {{ membership.plan_id }}
+ Started: {{ membership.created_at | slice: 0, 10 }}{% if membership.current_period_end %} · current period ends {{ membership.current_period_end | slice: 0, 10 }}{% endif %}{% if membership.canceled_at %} · canceled {{ membership.canceled_at | slice: 0, 10 }}{% endif %}
+ {% else %}No memberships found for this user.{% endfor %}{% if response.page_info.has_next_page %}Only the 10 most recent memberships are shown.{% endif %}{% else %}Whop returned an unexpected response.{% endif %}
+
+ - id: get_membership
+ title: Get Membership
+ description: Get one Whop membership with its status, billing period, cancellation details, and the link where the customer manages billing. Use a membership ID from List Memberships or List Payments.
+ http_method: GET
+ endpoint_url: https://api.whop.com/api/v1/memberships/{{ membership_id | strip | url_encode }}
+ auth_type: bearer
+ auth_config:
+ token: ${{ secrets.api_key }}
+ param_schema:
+ - name: membership_id
+ type: string
+ description: The Whop membership ID, starting with mem_.
+ required: true
+ response_template: |-
+ {% if response.id %}Membership {{ response.id }} · {{ response.status | replace: "_", " " }}
+ User ID: {{ response.user_id | default: "none" }}
+ Product ID: {{ response.product_id }} · plan ID: {{ response.plan_id }}
+ Started: {{ response.created_at | slice: 0, 10 }}{% if response.current_period_start %}
+ Current period: {{ response.current_period_start | slice: 0, 10 }} to {{ response.current_period_end | slice: 0, 10 }}{% endif %}{% if response.billing_period_days %}
+ Billed every {{ response.billing_period_days }} days{% endif %}
+ Cancels at period end: {% if response.cancel_at_period_end %}yes{% else %}no{% endif %}{% if response.canceled_at %}
+ Canceled: {{ response.canceled_at | slice: 0, 10 }}{% endif %}{% if response.cancellation_reason and response.cancellation_reason != "" %}
+ Cancellation reason: {{ response.cancellation_reason | newline_to_br | strip_newlines | replace: "
", " " | truncate: 300 }}{% endif %}{% if response.promo_code_id %}
+ Promo code ID: {{ response.promo_code_id }}{% endif %}{% if response.manage_url %}
+ Customer can manage billing at: {{ response.manage_url }}{% endif %}{% else %}Whop returned an unexpected response.{% endif %}
+
+ - id: list_payments
+ title: List Payments
+ description: List a customer's 10 most recent Whop payments with amount, status, failure reason, refunds, and what was bought. Use the user ID from Find Customer by Email. Call this for billing, failed payment, or refund questions.
+ http_method: GET
+ endpoint_url: https://api.whop.com/api/v1/payments?user_id={{ user_id | strip | url_encode }}&first=10&order=created_at&direction=desc
+ auth_type: bearer
+ auth_config:
+ token: ${{ secrets.api_key }}
+ param_schema:
+ - name: user_id
+ type: string
+ description: The Whop user ID from Find Customer by Email, starting with user_.
+ required: true
+ response_template: |-
+ {% if response.data %}{% for payment in response.data limit: 10 %}- {{ payment.created_at | slice: 0, 10 }} · {{ payment.total.amount | default: payment.subtotal.amount | default: "?" }} {{ payment.total.currency | default: payment.currency | upcase }} · {{ payment.substatus | default: payment.status | replace: "_", " " }}{% if payment.billing_reason %} · {{ payment.billing_reason | replace: "_", " " }}{% endif %}
+ Payment ID: {{ payment.id }}{% if payment.membership_id %} · membership ID: {{ payment.membership_id }}{% endif %}{% for line in payment.line_items limit: 3 %}{% if line.product_title or line.label %}
+ Item: {{ line.product_title | default: line.label | truncate: 80 }}{% if line.plan_title %} ({{ line.plan_title }}){% endif %}{% endif %}{% endfor %}{% if payment.failure_message and payment.failure_message != "" %}
+ Failure: {{ payment.failure_message | truncate: 200 }}{% if payment.decline_code %} (decline code: {{ payment.decline_code }}){% endif %}{% endif %}{% if payment.refunded_amount.amount %}
+ Refunded: {{ payment.refunded_amount.amount }} {{ payment.refunded_amount.currency | upcase }}{% if payment.refunded_at %} on {{ payment.refunded_at | slice: 0, 10 }}{% endif %}{% endif %}
+ {% else %}No payments found for this user.{% endfor %}{% if response.page_info.has_next_page %}Only the 10 most recent payments are shown.{% endif %}{% else %}Whop returned an unexpected response.{% endif %}
+
+ - id: get_payment
+ title: Get Payment
+ description: Get one Whop payment in full, with totals, tax, status, failure and retry details, refund state, and dispute alerts. Use a payment ID from List Payments, or one the customer quotes from a receipt.
+ http_method: GET
+ endpoint_url: https://api.whop.com/api/v1/payments/{{ payment_id | strip | url_encode }}
+ auth_type: bearer
+ auth_config:
+ token: ${{ secrets.api_key }}
+ param_schema:
+ - name: payment_id
+ type: string
+ description: The Whop payment ID, starting with pay_.
+ required: true
+ response_template: |-
+ {% if response.id %}Payment {{ response.id }} · {{ response.substatus | default: response.status | replace: "_", " " }}
+ Customer: {{ response.user.name | default: "No name" }}{% if response.customer_email %} <{{ response.customer_email }}>{% endif %}{% if response.user.id %} · user ID {{ response.user.id }}{% endif %}
+ Created: {{ response.created_at | slice: 0, 10 }}{% if response.paid_at %} · paid {{ response.paid_at | slice: 0, 10 }}{% endif %}
+ Total: {{ response.total.amount | default: "?" }} {{ response.total.currency | default: response.currency | upcase }}{% if response.subtotal.amount %} · subtotal {{ response.subtotal.amount }}{% endif %}{% if response.tax_amount.amount %} · tax {{ response.tax_amount.amount }}{% endif %}{% if response.billing_reason %}
+ Reason: {{ response.billing_reason | replace: "_", " " }}{% endif %}{% if response.payment_method_type %}
+ Payment method: {{ response.payment_method_type | replace: "_", " " }}{% endif %}{% for line in response.line_items limit: 10 %}{% if line.product_title or line.label %}
+ Item: {{ line.quantity | default: 1 | round }} x {{ line.product_title | default: line.label | truncate: 80 }}{% if line.plan_title %} ({{ line.plan_title }}){% endif %}{% if line.subtotal.amount %} · {{ line.subtotal.amount }}{% endif %}{% endif %}{% endfor %}{% if response.membership_id %}
+ Membership ID: {{ response.membership_id }}{% endif %}{% if response.product_id %}
+ Product ID: {{ response.product_id }}{% if response.plan_id %} · plan ID: {{ response.plan_id }}{% endif %}{% endif %}{% if response.failure_message and response.failure_message != "" %}
+ Failure: {{ response.failure_message | truncate: 300 }}{% if response.decline_code %} (decline code: {{ response.decline_code }}){% endif %}{% endif %}{% if response.next_payment_attempt_at %}
+ Next retry: {{ response.next_payment_attempt_at | slice: 0, 10 }}{% endif %}
+ Refunded: {% if response.refunded_amount.amount %}{{ response.refunded_amount.amount }} {{ response.refunded_amount.currency | upcase }}{% if response.refunded_at %} on {{ response.refunded_at | slice: 0, 10 }}{% endif %}{% else %}no{% endif %} · can still be refunded: {% if response.refundable %}yes{% else %}no{% endif %}{% if response.dispute_alerted_at %}
+ Dispute alert: {{ response.dispute_alerted_at | slice: 0, 10 }}{% endif %}{% else %}Whop returned an unexpected response.{% endif %}
+
+ - id: get_product
+ title: Get Product
+ description: Get a Whop product's name, headline, description, and visibility. Use a product ID from List Memberships, Get Membership, or Get Payment to tell the customer which product a membership or payment is for.
+ http_method: GET
+ endpoint_url: https://api.whop.com/api/v1/products/{{ product_id | strip | url_encode }}
+ auth_type: bearer
+ auth_config:
+ token: ${{ secrets.api_key }}
+ param_schema:
+ - name: product_id
+ type: string
+ description: The Whop product ID, starting with prod_.
+ required: true
+ response_template: |-
+ {% if response.id %}{{ response.title | default: "Untitled product" }} (product ID {{ response.id }})
+ Visibility: {{ response.visibility | default: "unknown" }}{% if response.headline and response.headline != "" %}
+ Headline: {{ response.headline | strip_newlines | truncate: 200 }}{% endif %}{% if response.description and response.description != "" %}
+ Description: {{ response.description | newline_to_br | strip_newlines | replace: "
", " " | truncate: 500 }}{% endif %}{% else %}Whop returned an unexpected response.{% endif %}
+
+ - id: get_plan
+ title: Get Plan
+ description: Get a Whop plan's pricing, with initial and renewal price, billing period, free trial, and access length. Use a plan ID from List Memberships, Get Membership, or Get Payment to explain what the customer is charged and how often.
+ http_method: GET
+ endpoint_url: https://api.whop.com/api/v1/variants/{{ plan_id | strip | url_encode }}
+ auth_type: bearer
+ auth_config:
+ token: ${{ secrets.api_key }}
+ param_schema:
+ - name: plan_id
+ type: string
+ description: The Whop plan ID, starting with plan_.
+ required: true
+ response_template: |-
+ {% if response.id %}{{ response.title | default: "Untitled plan" }} (plan ID {{ response.id }}){% if response.product.title %}
+ Product: {{ response.product.title }}{% if response.product.id %} (product ID {{ response.product.id }}){% endif %}{% endif %}
+ Type: {% if response.plan_type == "renewal" %}recurring{% else %}{{ response.plan_type | default: "unknown" | replace: "_", " " }}{% endif %}
+ Price: {{ response.formatted_price | default: "unknown" }}
+ Initial price: {{ response.initial_price | default: 0 }} {{ response.currency | upcase }}{% if response.plan_type == "renewal" %}
+ Renewal price: {{ response.renewal_price | default: 0 }} {{ response.currency | upcase }}{% if response.billing_period %} every {{ response.billing_period }} days{% endif %}{% endif %}{% if response.trial_period_days %}
+ Free trial: {{ response.trial_period_days }} days{% endif %}{% if response.expiration_days %}
+ Access lasts: {{ response.expiration_days }} days{% endif %}
+ Visibility: {{ response.visibility | default: "unknown" | replace: "_", " " }}{% if response.description and response.description != "" %}
+ Description: {{ response.description | newline_to_br | strip_newlines | replace: "
", " " | truncate: 300 }}{% endif %}{% else %}Whop returned an unexpected response.{% endif %}