diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7176535..a97de1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,6 +34,7 @@ The [publishing guide](https://www.chatwoot.com/captain/tools/publish) is the fu - **Use `response_template`.** Turn the response into short plain text with only the fields Captain needs. Limit long lists and truncate long text. Fall back to a message such as `Stripe returned an unexpected response.` when the expected fields are missing. - **Keep `endpoint_url` a literal URL.** `endpoint_url` must be a literal https:// URL. Use `{{ }}` placeholders with `url_encode` for dynamic parts; don't use `{% %}` tags in `endpoint_url`. Put any logic in `request_template` or `response_template` instead. - **Escape values in request bodies.** Call-time values are inserted into `request_template` as-is, so a quote in a parameter breaks the JSON. See `context-dev/toolset.yml` for a pattern that escapes quotes, backslashes, and newlines and passes the validator. +- **Protect customer data.** If a tool returns or changes a customer's own data, such as billing, bookings, or CRM records, add `requires_email_verification: true` so it runs only after the customer verifies their email with a one-time code. If the tool looks the customer up by email, give that parameter `source: verified.email` instead. Chatwoot then fills in the verified email, and Captain can't pass a different one. Captain's limits also shape what works: @@ -104,6 +105,7 @@ Fix every issue until the response has `"valid": true`. The validator runs the s - [ ] The README covers setup, required permissions, and every tool. - [ ] `version` is bumped (for updates to an existing integration). - [ ] The root README table and `.env.example` include the integration (for new integrations). +- [ ] Tools that return or change a customer's own data require email verification, and email lookups use `source: verified.email`. In the pull request description, say which tools you added or changed and how you tested them. diff --git a/attio/README.md b/attio/README.md index 5203b1e..2742e7d 100644 --- a/attio/README.md +++ b/attio/README.md @@ -10,7 +10,7 @@ A workspace admin can create an access token in Attio under **Workspace settings ## Tools -- **Find Person:** Find people by email address or phone number. Returns the person ID and company ID the other tools need. +- **Find Person:** Find the person for the customer's verified email address. Returns the person ID and company ID the other tools need. - **Get Company:** Check a company's details, including custom fields such as plan, tier, or renewal date. - **List Deals:** Check the 10 most recent deals linked to a person or company, with stage and value. - **List Notes:** Read the 5 most recent notes on a person, company, or deal. @@ -20,5 +20,6 @@ Start with Find Person, then use the person or company ID with the other tools. ## Notes +- Every tool runs only after the customer verifies their email with a one-time code in the conversation. Find Person always uses that verified email. - Find Person, Get Company, and List Deals return every field on the record, including custom fields, so Captain sees your workspace's own attributes. Owner and creator fields are left out because Attio returns them as member IDs, not names. - List Deals needs the Deals object to be enabled in your workspace. diff --git a/attio/toolset.yml b/attio/toolset.yml index 48e8c5e..37687d6 100644 --- a/attio/toolset.yml +++ b/attio/toolset.yml @@ -1,4 +1,4 @@ -version: 0.0.2 +version: 0.1.0 kind: captain_toolset name: Attio author: chatwoot @@ -14,7 +14,7 @@ secrets: tools: - id: find_person title: Find Person - description: Find people in Attio by email address or phone number. Use this first to get the person ID and company ID the other Attio tools need. + description: Find a person in Attio by email address. Use this first to get the person ID and company ID the other Attio tools need. http_method: POST endpoint_url: https://api.attio.com/v2/objects/people/records/query auth_type: bearer @@ -24,24 +24,22 @@ tools: - name: email type: string description: The person's email address. - required: false - - name: phone - type: string - description: The person's phone number in international format, such as +15558675309. - required: false + required: true + source: verified.email request_template: |- - {% capture bs %}\{% endcapture %}{% capture escaped_bs %}\\\\{% endcapture %}{% capture escaped_quote %}\"{% endcapture %}{% assign email = email | strip_newlines | strip | downcase | replace: bs, escaped_bs | replace: '"', escaped_quote %}{% assign phone = phone | strip_newlines | remove: " " | remove: "-" | remove: "(" | remove: ")" | replace: bs, escaped_bs | replace: '"', escaped_quote %}{% capture conditions %}{% if email != "" %},{"email_addresses":{"email_address":{"$eq":"{{ email }}"}}}{% endif %}{% if phone != "" %},{"phone_numbers":{"phone_number":{"$eq":"{{ phone }}"}}}{% endif %}{% endcapture %}{"filter":{% if conditions != "" %}{"$or":[{{ conditions | remove_first: "," }}]}{% else %}{"record_id":"00000000-0000-0000-0000-000000000000"}{% endif %},"limit":5} + {% capture bs %}\{% endcapture %}{% capture escaped_bs %}\\\\{% endcapture %}{% capture escaped_quote %}\"{% endcapture %}{% assign email = email | strip_newlines | strip | downcase | replace: bs, escaped_bs | replace: '"', escaped_quote %}{% if email != "" %}{"filter":{"email_addresses":{"email_address":{"$eq":"{{ email }}"}}},"limit":5}{% else %}{"filter":{"record_id":"00000000-0000-0000-0000-000000000000"},"limit":5}{% endif %} response_template: |- {% if response.data %}{% for record in response.data %}{{ record.values.name[0].full_name | default: "Unnamed person" }} (person ID {{ record.id.record_id }}) Open in Attio: {{ record.web_url }}{% for attribute in record.values %}{% if attribute[0] != "name" and attribute[1].size > 0 and attribute[1][0].attribute_type != "actor-reference" %}{% capture shown %}{% for v in attribute[1] limit: 5 %}{% case v.attribute_type %}{% when "personal-name" %}{{ v.full_name }}{% when "email-address" %}{{ v.email_address }}{% when "phone-number" %}{{ v.original_phone_number | default: v.phone_number }}{% when "domain" %}{{ v.domain }}{% when "currency" %}{{ v.currency_value }} {{ v.currency_code }}{% when "select" %}{{ v.option.title }}{% when "status" %}{{ v.status.title }}{% when "location" %}{% capture location %}{{ v.line_1 }} {{ v.locality }} {{ v.region }} {{ v.postcode }} {{ v.country_code }}{% endcapture %}{{ location | split: " " | join: " " }}{% when "record-reference" %}{{ v.target_object }} record {{ v.target_record_id }}{% when "interaction" %}{{ v.interaction_type }} on {{ v.interacted_at | slice: 0, 10 }}{% else %}{{ v.value }}{% endcase %}{% unless forloop.last %}; {% endunless %}{% endfor %}{% endcapture %}{% assign shown = shown | strip %}{% if shown != "" %} - {{ attribute[0] }}: {{ shown | truncate: 300 }}{% endif %}{% endif %}{% endfor %} {% unless forloop.last %} - {% endunless %}{% else %}No person found with that email address or phone number.{% endfor %}{% else %}Attio returned an unexpected response.{% endif %} + {% endunless %}{% else %}No person found with that email address.{% endfor %}{% else %}Attio returned an unexpected response.{% endif %} - id: get_company title: Get Company description: Get a company's details from Attio, including custom fields such as plan, tier, or renewal date. Use the company ID from Find Person. http_method: GET + requires_email_verification: true endpoint_url: https://api.attio.com/v2/objects/companies/records/{{ company_id | strip | url_encode }} auth_type: bearer auth_config: @@ -60,6 +58,7 @@ tools: title: List Deals description: List the 10 most recent deals in Attio linked to a person or company, with stage, value, and other deal fields. Use a person ID or company ID from Find Person. http_method: POST + requires_email_verification: true endpoint_url: https://api.attio.com/v2/objects/deals/records/query auth_type: bearer auth_config: @@ -82,6 +81,7 @@ tools: title: List Notes description: Read the 5 most recent notes on a person, company, or deal in Attio, such as call summaries or promises made by the team. Use a record ID from the other Attio tools. http_method: GET + requires_email_verification: true endpoint_url: https://api.attio.com/v2/notes?parent_object={{ record_type | strip | downcase | url_encode }}&parent_record_id={{ record_id | strip | url_encode }}&limit=50 auth_type: bearer auth_config: @@ -103,6 +103,7 @@ tools: title: Add Note description: Add a note to a person, company, or deal in Attio, for example a summary of this conversation or a follow-up request for the account owner. Only add a note when the conversation needs one. http_method: POST + requires_email_verification: true endpoint_url: https://api.attio.com/v2/notes auth_type: bearer auth_config: diff --git a/cal-com/README.md b/cal-com/README.md index 5386fe6..512fc18 100644 --- a/cal-com/README.md +++ b/cal-com/README.md @@ -8,7 +8,11 @@ Connect a Cal.com API key to help Captain find a customer's bookings and open ap ## Tools -- **Find Bookings by Email:** Find a customer's 10 most recent bookings using their email address. Each result includes the time, status, hosts, and location, plus reschedule and cancel links for active bookings. +- **Find Bookings by Email:** Find a customer's 10 most recent bookings using the customer's verified email address. Each result includes the time, status, hosts, and location, plus reschedule and cancel links for active bookings. - **Get Available Slots:** Check open times for the configured event type within a date range, in the customer's time zone. These tools set `cal-api-version: 2024-08-13` for all requests. That version supports booking search by email and the `/v2/slots/available` endpoint, which lets both tools share one API version. + +## Notes + +- Find Bookings by Email runs only after the customer verifies their email with a one-time code in the conversation, and always uses that verified email. diff --git a/cal-com/toolset.yml b/cal-com/toolset.yml index c6a102d..59ac1f5 100644 --- a/cal-com/toolset.yml +++ b/cal-com/toolset.yml @@ -1,4 +1,4 @@ -version: 0.0.1 +version: 0.1.0 kind: captain_toolset name: Cal.com author: chatwoot @@ -35,6 +35,7 @@ tools: type: string description: The email address used for the booking. required: true + source: verified.email response_template: |- {% if response.status != "success" %}Cal.com returned an unexpected response.{% elsif response.data.size == 0 %}No bookings found for this email address.{% else %}{% for booking in response.data %}- {{ booking.title }} Status: {{ booking.status }} diff --git a/stripe/README.md b/stripe/README.md index 2853fca..8b4b12a 100644 --- a/stripe/README.md +++ b/stripe/README.md @@ -6,7 +6,7 @@ Create a Stripe restricted API key with read access to Customers, Payment Intent ## Tools -- **Find Customers by Email:** Find customers by email address, ignoring case. Returns the customer ID the other tools need. +- **Find Customers by Email:** Find the customers for the customer's verified email address, ignoring case. Returns the customer ID the other tools need. - **List Payments:** Check a customer's 10 most recent payments, including decline reasons, refunds, card details, and receipt links. - **List Invoices:** Check a customer's 10 most recent finalized invoices, amounts due, and links to view, pay, or download them. Draft invoices are left out. - **List Subscriptions:** Check a customer's subscriptions, including canceled ones, with plan, price, renewal date, trial, and cancellation status. @@ -15,5 +15,6 @@ Start with Find Customers by Email, then use the customer ID with the list tools ## Notes +- Every tool runs only after the customer verifies their email with a one-time code in the conversation. Find Customers by Email always uses that verified email. - These tools set `Stripe-Version: 2026-08-26.dahlia` for all requests, so responses have the same shape regardless of your account's default API version. - Customer search uses the Stripe Search API. Stripe does not offer search to businesses in India, and new or updated customers can take up to a minute to appear. diff --git a/stripe/toolset.yml b/stripe/toolset.yml index c89208e..4adfcbc 100644 --- a/stripe/toolset.yml +++ b/stripe/toolset.yml @@ -1,4 +1,4 @@ -version: 0.0.1 +version: 0.1.0 kind: captain_toolset name: Stripe author: chatwoot @@ -28,6 +28,7 @@ tools: type: string description: The customer's email address. required: true + source: verified.email response_template: |- {% if response.object == "search_result" %}{% for customer in response.data %}- {{ customer.name | default: "No name" }} <{{ customer.email }}> Customer ID: {{ customer.id }} @@ -40,6 +41,7 @@ tools: title: List Payments description: Check a customer's 10 most recent payments, including failures, decline reasons, refunds, and receipt links. http_method: GET + requires_email_verification: true endpoint_url: https://api.stripe.com/v1/payment_intents?customer={{ customer_id | url_encode }}&limit=10&expand%5B%5D=data.latest_charge auth_type: bearer auth_config: @@ -63,6 +65,7 @@ tools: title: List Invoices description: Check a customer's 10 most recent finalized invoices, amounts due, and links to view, pay, or download them. http_method: GET + requires_email_verification: true endpoint_url: https://api.stripe.com/v1/invoices?customer={{ customer_id | url_encode }}&limit=100 auth_type: bearer auth_config: @@ -83,6 +86,7 @@ tools: title: List Subscriptions description: Check a customer's subscriptions, including plan, price, renewal date, trial, and cancellation status. http_method: GET + requires_email_verification: true endpoint_url: https://api.stripe.com/v1/subscriptions?customer={{ customer_id | url_encode }}&status=all&limit=10 auth_type: bearer auth_config: