Install
openclaw skills install @ivangdavila/stripe-api-integrationBuilds and debugs Stripe integrations: payments, subscriptions, Checkout, invoices, webhooks, Connect, disputes, tax. Use when a charge, refund, payout or subscription behaves wrong, when a webhook never arrives or its signature fails, when a card is declined or stuck on 3D Secure, when a customer was billed twice or the amount came out 100x off, when proration, trials, dunning or a plan change has to be exactly right, when splitting money across a marketplace with Connect, when disputes, chargebacks or fraud rules need work, when the payout does not match the bank, or when moving from test mode to live. Covers Payment Intents, Checkout Sessions, Payment Links, the Billing Portal, metered and tiered pricing, Stripe Tax, Radar, test clocks, idempotency, API versioning, and reconciliation. Not for choosing which payment provider to use (payments), PayPal integrations (paypal), App Store and Play Store purchases (in-app-purchases), or tracking the subscriptions you pay for as a consumer (subscriptions).
openclaw skills install @ivangdavila/stripe-api-integrationData. 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 four| Situation | Play | Depth |
|---|---|---|
| The customer was charged twice | Idempotency key scoped to the business action, not the HTTP retry; keys are only honored for 24h | api-mechanics.md |
| Webhook never arrives, or 400 on signature | Verify against the raw body, one secret per endpoint, 5-minute tolerance | webhooks.md |
| Charge succeeded but the app never provisioned | The provisioning path hangs off the event, not the API response | webhooks.md |
card_declined | The decline_code decides: retry later, retry never, or fix the request | debug.md |
Stuck on requires_action or 3D Secure | On-session vs off-session, and the authentication_required recovery loop | sca-3ds.md |
| Amount is 100x or 1000x off | Currency exponent, zero-decimal and three-decimal currencies | api-mechanics.md |
| Subscriptions dying at renewal | Involuntary churn: retry schedule, card updater, what the customer sees | dunning.md |
| Upgrade, downgrade, proration, billing anchor | Preview the invoice before you commit the change | subscriptions.md |
| Designing what to charge: tiers, seats, usage, packages | Price object shape decides what you can change later | pricing-models.md |
| Hosted payment page, trials without a card, upsells | Checkout Session modes and their event contract | checkout.md |
| Non-card money: SEPA, ACH, iDEAL, wallets, BNPL | Sync vs async settlement, refundability, dispute windows | payment-methods.md |
| Marketplace splitting money between sellers | Charge type decides fees, liability and who owns the customer | connect.md |
| A dispute arrived, or the dispute rate is climbing | Deadline, evidence packet, and the rate that triggers network programs | disputes.md |
| VAT, sales tax, reverse charge, invoice compliance | Registration first, then calculation, then the invoice fields | tax.md |
| Payout does not match the bank or the ledger | Reconcile from balance transactions, never from your own charge table | reconciliation.md |
| Customers, products, prices, coupons, promotion codes | Catalog objects and what is immutable once created | customers.md |
| Invoices, quotes, the Billing Portal, self-service changes | Finalization is the point of no return; the portal replaces most support tickets | invoices.md |
| Payment Intents, refunds, capture, SetupIntents, payouts | The object lifecycle and which transitions are one-way | payments.md |
| Simulating a renewal, a trial end, or a failed retry | Test clocks, test cards, and what test mode cannot prove | testing.md |
| First live charge, key rotation, migrating from another PSP | Card data migrates through Stripe, never through your database | go-live.md |
| Pagination, expand, versioning, rate limits, error types | Conventions that apply to every endpoint | api-mechanics.md |
| Issuing, Terminal, Treasury, Identity, Radar rules, Sigma | Products with their own onboarding, liability and pricing | advanced.md |
| Anything else Stripe | Answer 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.
| Signature | Most likely cause | First move |
|---|---|---|
No signatures found matching the expected signature | The body was parsed to JSON before verification, or the endpoint's own secret was not used | Verify against the raw bytes; each endpoint has a distinct whsec_ (webhooks.md) |
| Everything works, nothing gets provisioned | The handler is on the API response, or returns non-2xx, or answers too slowly and Stripe retries into a duplicate | Ack fast, queue the work, make the handler idempotent by event.id |
resource_missing on an id you can see in the Dashboard | Test id against a live key (or the reverse), or a Connect object without Stripe-Account | Check the key prefix and the account header before debugging anything else |
authentication_required on an off-session charge | The issuer wants the cardholder present | Do not retry off-session; bring them back on-session with the same PaymentIntent (sca-3ds.md) |
| Payment succeeded, then reversed days later | ACH or SEPA debit failed after acceptance | Treat bank debits as pending until the failure window closes (payment-methods.md) |
card_declined / do_not_honor | Issuer decision with no reason given to you | do_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 twice | New idempotency key per attempt, or a client retry with no key at all | Rule 3; then find both charges by metadata[order_id] |
idempotency_key_in_use or a mismatch error | The key was reused with a different body | Keys bind to a request body — new body means a new action means a new key |
HTTP 429 rate_limit | Bursty writes, usually a backfill or a migration script | Exponential backoff with jitter, cap concurrency (api-mechanics.md) |
Subscription is incomplete_expired | The first payment never completed within its window | It cannot be revived — create a new subscription (subscriptions.md) |
| Renewals quietly stopped for a cohort | Cards expired and no updater, or retries exhausted into unpaid | dunning.md |
| Money arrived somewhere unexpected in a marketplace | Charge type: direct, destination, or separate charges and transfers | connect.md |
| Payout is smaller than the sum of your charges | Fees, refunds, dispute withdrawals and reserve | reconciliation.md |
| Anything else | Every Stripe response carries a request id (req_…); that id opens the exact request, its parameters and its error in the Dashboard logs | debug.md |
Constraints that decide designs, not trivia — each one has broken an integration that was already written.
| Surface | Limit that decides the design |
|---|---|
| Idempotency | Keys are honored ~24h and are ≤255 chars — a retry queue that drains after a day duplicates money |
| Metadata | 50 keys per object, ~40-char keys, ~500-char values — metadata is an index, not a document store |
expand | Up to 4 levels deep, and webhook payloads cannot be expanded — re-fetch the object inside the handler when you need related data |
| Lists | 100 objects per page; cursor with starting_after, never offsets — offset paging over live data skips rows |
| Rate limits | Documented around 100 read and 100 write requests/second in live mode (lower in test) — bulk migrations need throttling, not luck |
| Webhook delivery | Signature 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 warnings |
| Checkout Session | Expires between 30 minutes and 24 hours; line items are capped (~20) — a big cart becomes one line item plus your own itemization |
| Subscription first payment | An incomplete subscription expires after ~23 hours and cannot be revived |
| Statement descriptor | ~22 characters total including the prefix — this string is what the cardholder recognizes or disputes |
| Minimum charge | Around 0.50 USD equivalent per currency — micro-transactions have to be aggregated before they are charged |
client_secret | A 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).
| Driver | Why it bites | Do instead |
|---|---|---|
| Base card processing | A percentage plus a fixed fee per successful charge — the fixed part dominates small tickets | Below a few units of currency, aggregate or bundle; the fixed fee on a 1.00 charge can exceed the percentage many times over |
| International and cross-border cards | An extra percentage on top of the base rate, plus a conversion fee when currencies differ | Present in the customer's currency only if you priced the conversion; otherwise the margin moves with FX |
| Disputes | A per-dispute fee (commonly 15 USD in the US) that you pay win or lose, on top of the reversed amount | Prevention beats evidence: descriptor recognition, 3DS on risky segments, refund before the dispute lands (disputes.md) |
| Product add-ons | Billing, Tax, Radar for Fraud Teams, Sigma and Revenue Recognition are priced on top of processing | Price them into the model; a percentage of recurring volume is a real line, not a rounding error |
| Instant payouts | A percentage of the payout amount for same-day money | Use standard payouts as the default; instant is a cash-flow decision, not a setting |
| Failed retries on bank debits | Some rails charge for the failure itself, so a bad dunning schedule bills you to lose the customer | Cap retries per rail and check the failure fee before increasing attempts (dunning.md) |
| Refunds | The original processing fee is generally not returned — a refunded sale still cost you | Full refunds are a support decision with a P&L; partial refunds cost the same fee |
| Currency conversion on payout | Converting balance to a different payout currency carries a spread | Hold a balance in the currencies you sell in where the account supports it |
| Connect application fees | Platform revenue is a fee on top of processing, and the liability side differs by charge type | Decide 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.
| Variable | Type | Default | Effect |
|---|---|---|---|
| stack | node | python | ruby | php | go | java | dotnet | http | http | Language of every code example; http means raw requests with no SDK assumed |
| api_version | text (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_currency | text (ISO 4217) | usd | Currency of every example and quote, and which exponent Rule 2 applies |
| billing_model | one-time | subscription | marketplace | invoicing | mixed | subscription | Which guide leads an ambiguous question, and which events the webhook baseline includes |
| live_mode_policy | test-only | confirm-each | free | confirm-each | Whether a live-key write is emitted at all, and with what confirmation step (Rule 9, Output Gates) |
| metadata_keys | list | [] | Keys attached to every created object in examples (Rule 4) |
| tax_handling | none | stripe_tax | external | none | Whether tax parameters appear in create calls and which invoice fields are treated as required (tax.md) |
| reconciliation_day | number (1-28) | 5 | Day 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 table| Trap | Why it fails | Do instead |
|---|---|---|
| Fulfilling on the API response | The customer closes the tab, the request times out, or the method settles asynchronously — the money is real and the order is not | Fulfill on the event, ack fast, deduplicate by event.id (Rule 1) |
| One webhook endpoint for everything, subscribed to all events | Volume you do not handle, a version you did not choose, and one bad handler poisoning unrelated flows | One endpoint per concern, explicit event list, pinned version (webhooks.md) |
amount * 100 everywhere | Correct for USD and EUR, 100x wrong for JPY and KRW, 10x wrong for KWD and BHD | Look up the exponent per currency (Rule 2) |
| Storing the subscription state in your own database as the truth | Stripe changes state on its own schedule — renewals, retries, cancellations at period end | Your database mirrors; Stripe decides; the events reconcile the two |
| Deleting a price or product to change it | Prices are immutable in the parts that matter, and live subscriptions point at them | Create the new price, migrate subscriptions deliberately (pricing-models.md) |
| Testing only the happy path in test mode | The expensive bugs are all in renewal, retry, dispute and payout paths | Test clocks for time, deliberate declines for failure (testing.md) |
| Treating a refund as a dispute cure | Refunding after the dispute is filed loses both the money and the fee, and can look like double repayment | Refund before the dispute if the signal arrived early; otherwise fight or accept, never both (disputes.md) |
| Retrying an off-session charge that asked for authentication | It will keep failing; the issuer wants the cardholder | Bring the customer back on-session (sca-3ds.md) |
| Reconciling by summing charges | Ignores fees, refunds, disputes, transfers and conversion; the number never matches the bank | Sum balance transactions per payout (Rule 8) |
| Using the same webhook secret across endpoints or environments | A test event verified by a live handler is an event you invented | One 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 migration | Decide from liability and reporting first (connect.md) |
| Rotating keys by creating a new one and forgetting the old | The old key keeps working, and it is the one that leaked | Roll, 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.