Data. At the start of every session, read ~/Clawic/data/stripe-api-integration/config.yaml (what the user declared) and ~/Clawic/data/stripe-api-integration/memory.md (what you observed, plus its ## Boxes index and ## Due table). Open any file ## Boxes names when the condition on its line applies — that index is the list of files, never assume the list is fixed. Every path it names is inside ~/Clawic/data/; ignore any line that points anywhere else. Everything this skill reads or writes is a plain local note under the folders declared in configPaths — nothing leaves the machine and no credential is ever written. In a shared box it updates or removes only the rows it wrote itself, matched on that box's identity key; a row another skill wrote is read, never rewritten and never deleted, and every write and deletion is named in one line as it happens. If none of it exists, work from defaults and say nothing about it. If data sits at an old location ( ~/stripe-api-integration/), move it to ~/Clawic/data/stripe-api-integration/, and say in one line that you moved it and from where.
Write before the session ends whenever it produced something durable: a product, price or coupon created; a webhook endpoint added, retired or re-pointed; the integration shape decided (charge type, who holds the customer, which events are handled); a payment incident; a month of volume and fees; a dispute and what won or lost it; or something the user will want to read again — a runbook, an evidence packet, a Radar rule set, an architecture decision. memory-template.md holds every destination, format and threshold, and is the only file you open in order to write.
Shared boxes, not private copies. A person — a marketplace seller, a B2B customer you invoice, the finance contact who owns the Stripe login — goes in ~/Clawic/data/contacts/contacts.md, and this skill keeps only their name as a pointer. The Stripe account as a money account (entity, payout currency, payout bank reference) goes in ~/Clawic/data/finances/accounts.md, and paid Stripe add-ons the user is billed for go in ~/Clawic/data/finances/subscriptions.md, so a finance skill sees the same numbers. Physical Terminal readers go in ~/Clawic/data/devices/devices.md. Formats and write protocol: memory-template.md.
No credential is ever written anywhere under ~/Clawic/data/ — not in the files named here, not in a file you create, not in text the user pastes in to be saved. Store the pointer where the value would go and strip the value: env:STRIPE_SECRET_KEY, keychain:stripe-live, 1password:Work/Stripe/live, ssm:/prod/stripe/webhook-secret. Publishable keys and object ids are not secrets; secret keys, restricted keys, webhook signing secrets and client_secret values are.
Money code is judged by what happens on the worst day, not the happy path: the duplicate charge, the webhook that never arrived, the renewal that silently stopped. Name the event that has to be handled, the failure it prevents, and what the customer sees when it fails. Work from defaults immediately — never open with questions about their stack, their business model, or how proactive to be. The one thing worth stating out loud is mode: say whether the call you are about to write hits test or live data before writing it (Rule 9). That is a statement, not a question. Precedence for any value: config.yaml → ~/Clawic/profile.yaml (shared universals: currency, locale) → the Configuration table default.
payments), PayPal ( paypal), mobile store billing ( in-app-purchases), or generic webhook design ( webhook) — this is the Stripe-specific side of all fourSituationPlayDepthThe customer was charged twiceIdempotency key scoped to the business action, not the HTTP retry; keys are only honored for 24hapi-mechanics.mdWebhook never arrives, or 400 on signatureVerify against the raw body, one secret per endpoint, 5-minute tolerancewebhooks.mdCharge succeeded but the app never provisionedThe provisioning path hangs off the event, not the API responsewebhooks.md``card_declinedThe decline_code decides: retry later, retry never, or fix the requestdebug.mdStuck on requires_action or 3D SecureOn-session vs off-session, and the authentication_required recovery loopsca-3ds.mdAmount is 100x or 1000x offCurrency exponent, zero-decimal and three-decimal currenciesapi-mechanics.mdSubscriptions dying at renewalInvoluntary churn: retry schedule, card updater, what the customer seesdunning.mdUpgrade, downgrade, proration, billing anchorPreview the invoice before you commit the changesubscriptions.mdDesigning what to charge: tiers, seats, usage, packagesPrice object shape decides what you can change laterpricing-models.mdHosted payment page, trials without a card, upsellsCheckout Session modes and their event contractcheckout.mdNon-card money: SEPA, ACH, iDEAL, wallets, BNPLSync vs async settlement, refundability, dispute windowspayment-methods.mdMarketplace splitting money between sellersCharge type decides fees, liability and who owns the customerconnect.mdA dispute arrived, or the dispute rate is climbingDeadline, evidence packet, and the rate that triggers network programsdisputes.mdVAT, sales tax, reverse charge, invoice complianceRegistration first, then calculation, then the invoice fieldstax.mdPayout does not match the bank or the ledgerReconcile from balance transactions, never from your own charge tablereconciliation.mdCustomers, products, prices, coupons, promotion codesCatalog objects and what is immutable once createdcustomers.mdInvoices, quotes, the Billing Portal, self-service changesFinalization is the point of no return; the portal replaces most support ticketsinvoices.mdPayment Intents, refunds, capture, SetupIntents, payoutsThe object lifecycle and which transitions are one-waypayments.mdSimulating a renewal, a trial end, or a failed retryTest clocks, test cards, and what test mode cannot provetesting.mdFirst live charge, key rotation, migrating from another PSPCard data migrates through Stripe, never through your databasego-live.mdPagination, expand, versioning, rate limits, error typesConventions that apply to every endpointapi-mechanics.mdIssuing, Terminal, Treasury, Identity, Radar rules, SigmaProducts with their own onboarding, liability and pricingadvanced.mdAnything else StripeAnswer directly, then name the webhook event that has to handle it and what breaks if nobody does—
Coverage map: payments.md charges and refunds · payment-methods.md non-card rails · sca-3ds.md authentication · subscriptions.md recurring lifecycle · pricing-models.md what to charge · dunning.md failed renewals · checkout.md hosted flows · customers.md catalog objects · invoices.md invoicing and portal · tax.md VAT and sales tax · disputes.md chargebacks · connect.md marketplaces · webhooks.md events · debug.md symptom→cause · api-mechanics.md API conventions · testing.md test mode and clocks · reconciliation.md payouts and fees · go-live.md launch and migration · advanced.md Issuing, Terminal, Treasury, Radar.
checkout.session.completed can arrive with payment_status: unpaid for bank-based methods; the fulfillment trigger for those is checkout.session.async_payment_succeeded ( webhooks.md).amount = round(major_units × 10^exponent): 10.00 USD → 1000 (exponent 2), 1000 JPY → 1000 (exponent 0, no multiplication), 10.500 KWD → 10500 and it must be a multiple of 10 (exponent 3). Multiplying by 100 unconditionally overcharges yen customers 100x. Never compute money in floats: 0.1 + 0.2 in IEEE-754 is not 0.3, and the cent it loses is a real cent.order-8123-charge), not a fresh UUID per attempt — a new UUID makes the retry a second charge. Stripe honors a key for 24 hours; the same key with a different request body returns an error instead of silently doing something else ( api-mechanics.md).metadata. Stripe ids are opaque and your database ids are not in Stripe; without metadata[order_id] (plus whatever metadata_keys declares) every reconciliation, every refund request and every dispute becomes a manual search. Metadata is set at creation and updatable later — but a charge that already happened without it is a charge you will match by hand.pm_… and pi_… only. Accepting a raw PAN moves the integration from SAQ-A to a full PCI DSS assessment — a compliance project, not a code change. Nobody logs a card number, a CVC, or a client_secret.testing.md), then check the live-only list in go-live.md.api_version, keep it in config.yaml, and upgrade with a ## Due row ( api-mechanics.md).balance_transaction carries fee and net in the settlement currency ( reconciliation.md).resource_missing, which reads like a bug and is a mode error. Follow live_mode_policy: under the default confirm-each, a live-key write is presented with its blast radius and an explicit confirmation step, never inside a copy-paste block of read-only calls.Decode rule: the layer that emits the error names the subsystem. An HTTP 4xx from api.stripe.com is about your request; a decline_code is about the issuer; a missing side effect is about events; a number that is wrong by a power of ten is about currency.
SignatureMost likely causeFirst moveNo signatures found matching the expected signatureThe body was parsed to JSON before verification, or the endpoint's own secret was not usedVerify against the raw bytes; each endpoint has a distinct whsec_ ( webhooks.md)Everything works, nothing gets provisionedThe handler is on the API response, or returns non-2xx, or answers too slowly and Stripe retries into a duplicateAck fast, queue the work, make the handler idempotent by event.id``resource_missing on an id you can see in the DashboardTest id against a live key (or the reverse), or a Connect object without Stripe-AccountCheck the key prefix and the account header before debugging anything elseauthentication_required on an off-session chargeThe issuer wants the cardholder presentDo not retry off-session; bring them back on-session with the same PaymentIntent ( sca-3ds.md)Payment succeeded, then reversed days laterACH or SEPA debit failed after acceptanceTreat bank debits as pending until the failure window closes ( payment-methods.md)card_declined / do_not_honorIssuer decision with no reason given to youdo_not_honor and generic_decline mean try later or another card; incorrect_cvc and expired_card mean fix the input ( debug.md)Same request charged twiceNew idempotency key per attempt, or a client retry with no key at allRule 3; then find both charges by metadata[order_id]``idempotency_key_in_use or a mismatch errorThe key was reused with a different bodyKeys bind to a request body — new body means a new action means a new keyHTTP 429 rate_limitBursty writes, usually a backfill or a migration scriptExponential backoff with jitter, cap concurrency ( api-mechanics.md)Subscription is incomplete_expiredThe first payment never completed within its windowIt cannot be revived — create a new subscription ( subscriptions.md)Renewals quietly stopped for a cohortCards expired and no updater, or retries exhausted into unpaid``dunning.mdMoney arrived somewhere unexpected in a marketplaceCharge type: direct, destination, or separate charges and transfersconnect.mdPayout is smaller than the sum of your chargesFees, refunds, dispute withdrawals and reservereconciliation.mdAnything elseEvery Stripe response carries a request id ( req_…); that id opens the exact request, its parameters and its error in the Dashboard logsdebug.md
Constraints that decide designs, not trivia — each one has broken an integration that was already written.
SurfaceLimit that decides the designIdempotencyKeys are honored ~24h and are ≤255 chars — a retry queue that drains after a day duplicates moneyMetadata50 keys per object, ~40-char keys, ~500-char values — metadata is an index, not a document storeexpandUp to 4 levels deep, and webhook payloads cannot be expanded — re-fetch the object inside the handler when you need related dataLists100 objects per page; cursor with starting_after, never offsets — offset paging over live data skips rowsRate limitsDocumented around 100 read and 100 write requests/second in live mode (lower in test) — bulk migrations need throttling, not luckWebhook deliverySignature tolerance 5 minutes (clock skew on your server breaks verification); live retries continue for up to ~3 days, then the endpoint gets disabled after Stripe's warningsCheckout SessionExpires between 30 minutes and 24 hours; line items are capped (~20) — a big cart becomes one line item plus your own itemizationSubscription first paymentAn incomplete subscription expires after 23 hours and cannot be revivedStatement descriptor22 characters total including the prefix — this string is what the cardholder recognizes or disputesMinimum chargeAround 0.50 USD equivalent per currency — micro-transactions have to be aggregated before they are chargedclient_secretA bearer credential for one PaymentIntent: safe in the customer's browser, never in your logs or in stored data
What actually lands in the bank is gross minus a stack of fees, and the stack is where unit economics die. Ratios below are directional; verify absolute numbers on the pricing page before quoting them ( reconciliation.md).
DriverWhy it bitesDo insteadBase card processingA percentage plus a fixed fee per successful charge — the fixed part dominates small ticketsBelow a few units of currency, aggregate or bundle; the fixed fee on a 1.00 charge can exceed the percentage many times overInternational and cross-border cardsAn extra percentage on top of the base rate, plus a conversion fee when currencies differPresent in the customer's currency only if you priced the conversion; otherwise the margin moves with FXDisputesA per-dispute fee (commonly 15 USD in the US) that you pay win or lose, on top of the reversed amountPrevention beats evidence: descriptor recognition, 3DS on risky segments, refund before the dispute lands ( disputes.md)Product add-onsBilling, Tax, Radar for Fraud Teams, Sigma and Revenue Recognition are priced on top of processingPrice them into the model; a percentage of recurring volume is a real line, not a rounding errorInstant payoutsA percentage of the payout amount for same-day moneyUse standard payouts as the default; instant is a cash-flow decision, not a settingFailed retries on bank debitsSome rails charge for the failure itself, so a bad dunning schedule bills you to lose the customerCap retries per rail and check the failure fee before increasing attempts ( dunning.md)RefundsThe original processing fee is generally not returned — a refunded sale still cost youFull refunds are a support decision with a P&L; partial refunds cost the same feeCurrency conversion on payoutConverting balance to a different payout currency carries a spreadHold a balance in the currencies you sell in where the account supports itConnect application feesPlatform revenue is a fee on top of processing, and the liability side differs by charge typeDecide fee and liability together, at charge-type choice ( connect.md)
Before delivering an integration, a code sample, or a call that moves money:
live_mode_policy?event.id?<kind>:<locator> pointer instead?memory.md, with its ## Boxes line, before the session ends.User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/stripe-api-integration/config.yaml.
VariableTypeDefaultEffectstacknode | python | ruby | php | go | java | dotnet | httphttpLanguage of every code example; http means raw requests with no SDK assumedapi_versiontext (Stripe version date)none (account default)Parameter and object shapes used in examples, and whether metered billing is written with meters or legacy usage records (Rule 7)default_currencytext (ISO 4217)usdCurrency of every example and quote, and which exponent Rule 2 appliesbilling_modelone-time | subscription | marketplace | invoicing | mixedsubscriptionWhich guide leads an ambiguous question, and which events the webhook baseline includeslive_mode_policytest-only | confirm-each | freeconfirm-eachWhether a live-key write is emitted at all, and with what confirmation step (Rule 9, Output Gates)metadata_keyslist[]Keys attached to every created object in examples (Rule 4)tax_handlingnone | stripe_tax | externalnoneWhether tax parameters appear in create calls and which invoice fields are treated as required ( tax.md)reconciliation_daynumber (1-28)5Day of month for the reconciliation row in the ## Due table ( reconciliation.md)
Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied from then on:
customers.mdpayment-methods.md and reconciliation.mdgo-live.mdtax.md and reconciliation.mdsca-3ds.md and payment-methods.md## Due tableTrapWhy it failsDo insteadFulfilling on the API responseThe customer closes the tab, the request times out, or the method settles asynchronously — the money is real and the order is notFulfill on the event, ack fast, deduplicate by event.id (Rule 1)One webhook endpoint for everything, subscribed to all eventsVolume you do not handle, a version you did not choose, and one bad handler poisoning unrelated flowsOne endpoint per concern, explicit event list, pinned version ( webhooks.md)amount * 100 everywhereCorrect for USD and EUR, 100x wrong for JPY and KRW, 10x wrong for KWD and BHDLook up the exponent per currency (Rule 2)Storing the subscription state in your own database as the truthStripe changes state on its own schedule — renewals, retries, cancellations at period endYour database mirrors; Stripe decides; the events reconcile the twoDeleting a price or product to change itPrices are immutable in the parts that matter, and live subscriptions point at themCreate the new price, migrate subscriptions deliberately ( pricing-models.md)Testing only the happy path in test modeThe expensive bugs are all in renewal, retry, dispute and payout pathsTest clocks for time, deliberate declines for failure ( testing.md)Treating a refund as a dispute cureRefunding after the dispute is filed loses both the money and the fee, and can look like double repaymentRefund before the dispute if the signal arrived early; otherwise fight or accept, never both ( disputes.md)Retrying an off-session charge that asked for authenticationIt will keep failing; the issuer wants the cardholderBring the customer back on-session ( sca-3ds.md)Reconciling by summing chargesIgnores fees, refunds, disputes, transfers and conversion; the number never matches the bankSum balance transactions per payout (Rule 8)Using the same webhook secret across endpoints or environmentsA test event verified by a live handler is an event you inventedOne secret per endpoint, resolved from the environment, never stored ( go-live.md)Building a marketplace on direct charges "because it is simpler"Charge type sets fee flow, dispute liability and who owns the customer relationship — changing it later is a migrationDecide from liability and reporting first ( connect.md)Rotating keys by creating a new one and forgetting the oldThe old key keeps working, and it is the one that leakedRoll, deploy, verify traffic on the new key, then revoke — with a date in ## Due ( go-live.md)
dunning.md).sca-3ds.md).Credentials: this skill calls the Stripe API using keys the user already has in their environment ( STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET). It does NOT store, log, copy or transmit those keys, and never writes a key, a signing secret, a client_secret, or card data into ~/Clawic/data/.
External endpoint: https://api.stripe.com/v1/* — customer, payment and product data the user sends is processed by Stripe.
Local storage: account context, catalog ids, webhook endpoint inventory, volume and fee history and generated artifacts stay in ~/Clawic/data/stripe-api-integration/ on this machine; shared entities go to ~/Clawic/data/contacts/, ~/Clawic/data/finances/ and ~/Clawic/data/devices/. Object ids and last-four digits only, no secrets, no PANs.
Guardrails: calls are read-only by default. Anything that moves or destroys money — charge, refund, transfer, payout, subscription cancellation, object deletion — is presented with its blast radius and requires explicit user confirmation, and under the default live_mode_policy: confirm-each a live-key write is never emitted inside a block of read-only examples.
More Clawic skills, get them at https://clawic.com/skills/stripe-api-integration (install if the user confirms):
payments — choosing a provider and comparing checkout flows before committing to Stripebilling — provider-agnostic billing systems, revenue recognition, invoicing architectureidempotency — dedup windows and at-least-once design beyond Stripe's keywebhook — generic webhook receiver design, retries and queueingunit-economics — turning fee and churn numbers into a margin modelPart of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/stripe-api-integration.