Skip to content
Browse the documentation
Connectors

Stripe

What the Stripe connector reads, with which permissions, what it never touches, how it syncs, and the facts it produces.

Last changed Markdown versionllms.txt

Source module: app/connectors/stripe_conn.py. Kind: facts only. Display name: "Stripe".

This is the data connector that reads a customer's own Stripe account for refund/credit facts. It is unrelated to CommitLayer's own subscription billing (app/billing/stripe_billing.py), which uses the platform's Stripe account and separate keys.

Purpose

Reads refunds, credit notes and subscription cancellations so that the refund_or_credit_within, refund_posted_within and subscription-related signals can be evaluated against claims from help-desk vendors.

Since 0.9.5 it also reads outbound payments from the account's financial accounts (Treasury v1 and Money Management v2, the API behind Global Payouts and stripe pay), so that a transfer an agent sends can be verified under the stripe_b2b_transfer library contract: did it exist, go to the intended recipient, once, for the amount asked, reach posted, and stay there.

Auth and scopes

Item Value
Method Restricted API key, sent as Authorization: Bearer, against https://api.stripe.com
Credentials stored (encrypted) api_key; optional base_url
Least-privilege permissions for the restricted key Refunds: read, Credit notes: read, Subscriptions: read, Customers: read (needed only to expand the customer on refunds/credit notes for email hashing), Charges: read (implied by expanding charge.customer on refunds). To verify transfers an agent sends, add Financial accounts: read and Outbound payments: read (Treasury), and Money Management Outbound Payments: read (v2). Everything else: none. A key without the transfer permissions still syncs refunds; the connection's health message says what to grant.
Connection test GET /v1/refunds?limit=1
Non-secret config none

Fields pulled and why

Endpoint Fields Used for
GET /v1/refunds?created[gt]=since&expand[]=data.charge.customer id, amount, currency, created, charge.customer.id, charge.customer.email Refund facts, subject id, hashed email, watermark
GET /v1/credit_notes?created[gt]=since&expand[]=data.customer id, amount, currency, created, customer.id, customer.email Credit facts, subject id, hashed email
GET /v1/subscriptions?created[gt]=since id, status, canceled_at, customer Subscription-cancellation facts
GET /v1/treasury/financial_accounts then GET /v1/treasury/outbound_payments?financial_account=&created[gt]=since-35d id, amount, currency, status, status_transitions, customer / destination_payment_method, financial_account, created, returned_details.code Outbound-payment facts (Treasury v1)
GET /v2/money_management/outbound_payments?created_gt=since-35d (header Stripe-Version: 2026-04-22.preview, pages followed by next_page_url) id, amount.value/currency, status, status_transitions, to.recipient / to.payout_method, from.financial_account, created, status_details.returned.reason Outbound-payment facts (Money Management v2)

Amounts are converted from minor units (amount / 100). Outbound payments are re-read 35 days behind the watermark on every sync, because a payment changes status after it is created (posted, returned) and the facts for those statuses are keyed on the payment; the re-read is idempotent.

What is hashed / never stored

  • Customer email (from the expanded customer object) → customer_hid (HMAC-SHA256, per-tenant key); the email is discarded.
  • Card details, payment method data, customer names, addresses, invoice line items, charge descriptions and metadata are never requested or stored.
  • The restricted key is stored only as ciphertext.

Incremental sync and watermark

  • All three lists use created[gt] = <watermark epoch> (0 on first sync), page size 100, cursor via starting_after = <last id> while has_more.
  • Watermark = maximum refund created (credit notes and subscriptions do not advance it).
  • Upserts are idempotent.

Rate-limit handling

Shared HttpClient (Retry-After, exponential backoff on 429/5xx, 6 attempts, immediate reauth failure on 401/403).

Fact types produced

Subject id is the Stripe customer: stripe_customer_{customer_id}.

fact_type Subject kind When Attributes raw_ref
refund customer Each refund amount (major units), currency, customer_hid stripe:refund:{refund_id}
credit customer Each credit note amount, currency, customer_hid stripe:credit_note:{credit_note_id}
subscription_change subscription Each listed subscription with canceled_at set status, change: "canceled" stripe:subscription:{subscription_id}:canceled
outbound_payment_created recipient Each outbound payment, at created; attributes refresh on every sync amount, currency, payment_id, status, financial_account stripe:outbound_payment:{id}
outbound_payment_posted recipient Status posted, at status_transitions.posted_at amount, currency, payment_id stripe:outbound_payment:{id}:posted
outbound_payment_returned recipient Status returned, at returned_at amount, currency, payment_id, reason stripe:outbound_payment:{id}:returned
outbound_payment_failed recipient Status failed or canceled, at failed_at / canceled_at amount, currency, payment_id, status stripe:outbound_payment:{id}:{status}

Outbound-payment facts use the subject stripe_recipient_{id}, where the id is the recipient as Stripe names it: to.recipient (an acct_…) or to.payout_method on v2, the customer or destination_payment_method on v1. An agent that records a stripe_b2b_transfer action uses the same subject (the --json output of stripe pay carries it), or pushes a links entry from the profile handle it was told to pay to that id.

Claims produced

None.

Subject id format

stripe_customer_{cus_…}. Because help-desk claims name a conversation or ticket, Stripe facts only become relevant to a claim through a subject link between the conversation subject and the Stripe customer subject. The link is made by matching customer_hid (hashed email) between the two sources; the library function app.connectors.base.resolve_identities performs this, and a scheduled identity-resolution pass is a planned addition (see ARCHITECTURE.md §4). Until then, links can be supplied via a CSV facts upload whose subject_id equals the conversation subject.

Known limitations

  • Subscription cancellations are discovered through created[gt], i.e. only subscriptions created after the watermark are listed; cancellations of older subscriptions are missed. This is a known gap.
  • Refunds without an expandable charge/customer (e.g. some Checkout or PaymentIntent flows) produce a subject id with an empty customer id and cannot be linked.
  • No customer_hid on subscription facts (customer is referenced by id only).
  • Partial refunds and full refunds are not distinguished in the fact type; amount is available in attributes.
  • Disputes/chargebacks are not read.
  • A recipient Stripe has never paid has no record at all, so an agent's claim to have paid it, when nothing was created, answers UNKNOWN (identity_unmatched, "subject not found") rather than FAIL; once any payment to that recipient exists, a missing one is a FAIL. The v2 list is read under a pinned preview API version (V2_VERSION in the module); Stripe may move it.
  • Financial-account balances are not read: Stripe's posted status is its own statement that funds left the account, and a balance snapshot taken at sync time cannot be aligned with the action's moment.