stripe-node accepts only
host, port, and protocol, not a base path, so point it at Stripe’s own port.At a glance
Start
http://localhost:4009, or on http://localhost:4000 when it is the only service. Without a seed config there is one customer ([email protected], “Test Customer”).
stripe-node does not read an environment variable for the base URL, so pass the emulator’s host and port when constructing the client:
Requests
These rules apply to every/v1 endpoint, as they do on Stripe:
- Idempotency. A POST with an
Idempotency-Keyheader (1 to 255 characters) saves its result for 24 hours. A retry with the same key and the same parameters gets the saved response back withIdempotent-Replayed: trueandOriginal-Request: <first request id>, without running the endpoint again, so no second object or event is created. The same key with other parameters, or on another endpoint, gets a 400idempotency_error(Stripe.errors.StripeIdempotencyErrorin stripe-node). A retry while the first request is still running gets a 409idempotency_key_in_use. A request that fails validation (a 4xx other than 402) is not saved, so the key can be retried with fixed parameters. stripe-node adds a key to every POST on its own. - Request IDs. Every response carries a
Request-Id: req_...header (lastResponse.requestIdin stripe-node). - Expand.
expand[]works on query strings and request bodies, including create and update calls. Lists takedata.<field>, e.g.expand[]=data.customer. A field that cannot be expanded returns 400This property cannot be expanded (<field>). - Metadata. Updates merge
metadatainto what is stored. Set a key to""to remove it, or sendmetadata=""to clear all keys. Metadata values always stay strings. - Form values. Booleans sent as
true/falseare decoded, soactive=falsestores a boolean.
Customers
POST /v1/customers— Create customerGET /v1/customers/:id— Retrieve customerPOST /v1/customers/:id— Update customerDELETE /v1/customers/:id— Delete customerGET /v1/customers— List customers
test_clock on create puts the customer on a test clock (at most three per clock). As on Stripe, GET /v1/customers lists customers on a clock only when test_clock is given.
balance (create and update) is the customer’s credit (negative) or debt (positive). Finalizing an invoice applies it, as described under Invoices.
Deleting a customer cancels their subscriptions first, sending customer.subscription.deleted for each.
Customer balance
POST /v1/customers/:id/balance_transactions— Adjust customer balance: byamount(negative is credit) incurrency, withdescriptionandmetadataGET /v1/customers/:id/balance_transactions/:txn— Retrieve balance transactionPOST /v1/customers/:id/balance_transactions/:txn— Update balance transaction:description,metadataGET /v1/customers/:id/balance_transactions— List balance transactions: the ledger, newest first (filter byinvoice)
balance is recorded with the balance after it (ending_balance): initial for balance on create, adjustment for manual entries and balance on update, applied_to_invoice when finalizing uses it, unapplied_from_invoice when voiding gives it back, and credit_note for credit notes.
Payment methods
POST /v1/payment_methods— Create payment method: a card from a test token (type=card,card[token]=tok_visa;billing_details,metadataoptional)GET /v1/payment_methods/:id— Retrieve payment methodPOST /v1/payment_methods/:id— Update payment method:billing_details,metadata, orcard[exp_month]/card[exp_year](attached payment methods only)POST /v1/payment_methods/:id/attach— Attach payment method: to acustomerPOST /v1/payment_methods/:id/detach— Detach payment method: from its customerGET /v1/payment_methods— List payment methods:customer,type; withoutcustomer, unattached onesGET /v1/customers/:id/payment_methods— List a customer’s payment methodsGET /v1/customers/:id/payment_methods/:pm— Retrieve a customer’s payment method
brand, last4, funding, country, exp_month, exp_year) for the test card they stand for. As on Stripe, passing a magic test ID such as pm_card_visa to a payment intent, setup intent, or attach makes a new payment method with its own pm_... ID, and that ID is what responses show. Tokens such as tok_visa and tok_chargeDeclined work with card[token]. Raw card numbers get Stripe’s “sending credit card numbers directly … is generally unsafe” error. An ID that is neither stored nor a test ID returns 400 No such PaymentMethod.
Attaching checks the card, so a card that declines (pm_card_visa_chargeDeclined) cannot be saved; pm_card_chargeCustomerFail saves and then declines every charge. A saved card can only pay for its own customer: a payment or setup without that customer, or with another one, is refused. Customers have invoice_settings.default_payment_method, set on create or update to a card attached to them; detaching that card or deleting the customer clears it. Customer create also takes payment_method to attach a card. Events: payment_method.attached, payment_method.detached, payment_method.updated.
Setup intents
POST /v1/setup_intents— Create setup intent:customer,payment_method,usagedefaultoff_session,confirm,return_url,description,metadataGET /v1/setup_intents/:id— Retrieve setup intentPOST /v1/setup_intents/:id— Update setup intent:customer,payment_method,description,metadataPOST /v1/setup_intents/:id/confirm— Confirm setup intent:payment_method,return_urloptionalPOST /v1/setup_intents/:id/cancel— Cancel setup intent:cancellation_reasonoptionalGET /v1/setup_intents— List setup intents: filter bycustomer,payment_method
setup_intent.succeeded; with usage: off_session the card is set up for off-session payments. A card that declines fails the setup with a 402 (setup_intent.setup_failed, last_setup_error), and a card that needs authentication goes to requires_action with the same hosted 3D Secure page as payments. After pm_card_authenticationRequiredOnSetup is set up, off-session payments with it succeed without authentication; pm_card_authenticationRequired keeps asking. Payment intents can save the card as they pay with setup_future_usage (on_session or off_session), which attaches it to the intent’s customer once the payment succeeds.
Customer sessions
POST /v1/customer_sessions— Create customer session
Payment intents
POST /v1/payment_intents— Create payment intentGET /v1/payment_intents/:id— Retrieve payment intentPOST /v1/payment_intents/:id— Update payment intentPOST /v1/payment_intents/:id/confirm— Confirm payment intentPOST /v1/payment_intents/:id/capture— Capture payment intent: an authorized one (amount_to_captureoptional)POST /v1/payment_intents/:id/cancel— Cancel payment intent:cancellation_reasonoptionalGET /v1/payment_intents— List payment intents
capture_method: "automatic_async"), creating a captured charge and setting amount_received and latest_charge. Create with confirm: true to create and confirm in one call. With capture_method: "manual", confirming authorizes instead: the intent moves to requires_capture with amount_capturable set, the charge has captured: false, and payment_intent.amount_capturable_updated is sent. Capture all or part of it with /capture; a partial capture releases the rest, which shows in the charge’s amount_refunded. Canceling an authorized intent releases the hold, so its charge reads as fully refunded and charge.refunded is sent.
Stripe payment intents and Checkout sessions follow API version 2026-09-30.endive (stripe-node 23): send allowed_payment_method_types, which responses echo, and read the usable list from payment_method_types, which defaults to ["card"]. The older payment_method_types request param is still accepted for clients pinned to earlier versions.
Test cards
Confirming needs a payment method; without one, confirm returns 400payment_intent_unexpected_state, as on Stripe. Stripe’s test payment method IDs and the matching tok_ tokens behave as they do on Stripe. Other pm_card_ IDs, such as country cards, are cards that succeed. The dispute cards succeed and then open a dispute.
A decline returns a 402 card error (
Stripe.errors.StripeCardError in stripe-node) with code, decline_code, charge, payment_method, and payment_intent. It leaves a failed charge with failure_code, failure_message, outcome, and payment_method_details, sets the intent’s last_payment_error, returns it to requires_payment_method, and sends charge.failed and payment_intent.payment_failed. Confirm again with another payment_method to retry. With an Idempotency-Key, the 402 is replayed like any other result.
3D Secure
A card that needs authentication moves the intent torequires_action, sends payment_intent.requires_action, and sets next_action to the hosted page at /_stripe/3ds/:id: redirect_to_url when the intent has a return_url, otherwise use_stripe_sdk. The page has Complete and Fail buttons. Completing applies the card’s result (success, requires_capture for manual capture, or a decline); failing returns the intent to requires_payment_method with payment_intent_authentication_failure. With a return_url, the page redirects there with payment_intent, payment_intent_client_secret, and redirect_status (succeeded or failed).
Tests can skip the page and POST the outcome; Accept: application/json returns the payment intent:
Charges
GET /v1/charges/:id— Retrieve chargeGET /v1/charges— List charges
Refunds
POST /v1/refunds— Create refund: of a captured payment, bypayment_intentorcharge(amount,reason,metadataoptional)GET /v1/refunds/:id— Retrieve refundPOST /v1/refunds/:id— Update refund metadataPOST /v1/refunds/:id/cancel— Cancel refund: refunds succeed at once here, so this returns Stripe’s error for a refund that is not pendingGET /v1/refunds— List refunds: filter bycharge,payment_intent
amount refunds what is left. Partial refunds add up in the charge’s amount_refunded until refunded is true. Refunding more than is left returns amount_too_large, a fully refunded charge returns charge_already_refunded, and an uncaptured charge returns charge_not_refundable (cancel its payment intent instead). Each refund sends refund.created and charge.refunded. A charge with a chargeback cannot be refunded (charge_disputed); an open inquiry can.
Balance
GET /v1/balance— Retrieve balance:availableandpendingfunds per currencyGET /v1/balance_transactions/:id— Retrieve balance transaction:expand[]=sourceGET /v1/balance_transactions— List balance transactions: filter bytype,source,currency,created
balance_transaction; expand it, or latest_charge.balance_transaction on a payment intent, to read fees. A charge adds its amount less Stripe’s US card fee (2.9% + 30 cents, in fee_details) and is pending until available_on, two days after midnight UTC. A refund takes its amount out at once, and Stripe keeps the fee. A manual capture records the captured amount when it is captured. The balance is the sum of net amounts, split by whether available_on has passed.
Disputes
GET /v1/disputes/:id— Retrieve disputePOST /v1/disputes/:id— Update dispute: stage or submitevidence, updatemetadataPOST /v1/disputes/:id/close— Close dispute: accept itGET /v1/disputes— List disputes: filter bycharge,payment_intent
disputed. A chargeback starts as needs_response and withdraws the amount plus a $15 fee at once (an adjustment balance transaction, charge.dispute.funds_withdrawn). An inquiry starts as warning_needs_response and moves no funds.
Evidence is submitted when sent, unless submit=false stages it. Submitting moves the dispute to under_review (warning_under_review for an inquiry), after which evidence cannot change. As on Stripe, evidence[uncategorized_text] decides the outcome: winning_evidence wins (won, the amount and fee come back with charge.dispute.funds_reinstated; an inquiry becomes warning_closed), and losing_evidence loses (lost; a lost inquiry withdraws the funds then). Other evidence stays under review. Closing accepts the dispute: a chargeback is lost, an inquiry warning_closed. Events: charge.dispute.created, .updated, .closed, .funds_withdrawn, .funds_reinstated.
Connect
POST /v1/accounts— Create connected account:type(standard, the default,express, orcustom) orcontroller(stripe_dashboard[type]offull,express, ornone),country,email,business_type,business_profile,capabilities,tos_acceptance,external_account,metadataGET /v1/accounts/:id— Retrieve connected accountPOST /v1/accounts/:id— Update connected accountDELETE /v1/accounts/:id— Delete connected accountGET /v1/accounts— List connected accountsGET /v1/account— Retrieve current account: the account inStripe-Account, or the platform (acct_baseline) without itPOST /v1/account_links— Create account link: hosted onboarding (account_onboarding, oraccount_updatefor Custom accounts) withrefresh_urlandreturn_urlPOST /v1/accounts/:id/login_links— Create login link: an Express dashboard link, for onboarded Express accountsPOST /v1/transfers— Create transferGET /v1/transfers/:id— Retrieve transferPOST /v1/transfers/:id— Update transfer:description,metadataGET /v1/transfers— List transfers: filter bydestination,transfer_groupGET /v1/application_fees/:id— Retrieve application feeGET /v1/application_fees— List application fees: filter bycharge
business_type, external_account, tos_acceptance.date, and tos_acceptance.ip (requirements.currently_due); until then charges_enabled and payouts_enabled are false and its capabilities are inactive. The account link page fills in what is missing when the user clicks Complete onboarding, then redirects to return_url; a used or expired link (after five minutes) redirects to refresh_url. Tests can POST the link URL with Accept: application/json to get the account back. Custom accounts can also be completed through POST /v1/accounts/:id; any external_account value counts as a payout account. Changes send account.updated.
A request with Stripe-Account: acct_... acts on that account’s own data: customers, payment methods, payment intents, charges, refunds, disputes, billing, balance, and events are kept per account. An unknown or deleted account returns 403 account_invalid. The hosted Checkout and 3D Secure pages find a connected account’s sessions and intents on their own. Events on a connected account carry account and go to webhook endpoints created with connect=true (or seeded with connect: true), not the platform’s.
Charges move money as on Stripe:
- Direct charge (with
Stripe-Account): the charge is the account’s.application_fee_amountis taken out of its balance transaction (fee_detailslistsstripe_feeandapplication_fee) and paid to the platform as an application fee (application_fee.created). - Destination charge (
transfer_data[destination]): the charge is the platform’s. The amount, ortransfer_data[amount], is transferred to the account (transfer.created, the charge’stransfer), andapplication_fee_amountcomes back to the platform out of the account’spayment. The destination must be onboarded withtransfers(insufficient_capabilities_for_transfer).application_fee_amountwithout either returns 400, as does combining it withtransfer_data[amount]. - Separate charges and transfers:
POST /v1/transfersneeds that much available platform balance (balance_insufficient; pay withpm_card_bypassPendingto skip the pending period), or asource_transactioncharge, whose funds it waits for.
on_behalf_of and transfer_group are kept and echoed.
Products
POST /v1/products— Create productGET /v1/products/:id— Retrieve productGET /v1/products— List products
Prices
POST /v1/prices— Create price:unit_amountrequired;recurring,lookup_key,nicknameoptionalGET /v1/prices/:id— Retrieve priceGET /v1/prices— List prices: filter byproduct,active,type,lookup_keys[]
recurring[interval] (day, week, month, year) with recurring[interval_count] makes a recurring price, up to Stripe’s three-year limit. Only licensed (per unit) prices are emulated: usage_type=metered returns 400.
Coupons and promotion codes
POST /v1/coupons— Create coupon:percent_offoramount_offwithcurrency,duration(once,repeatingwithduration_in_months, orforever),max_redemptions,redeem_by,applies_to[products][],id(otherwise a random code),nameGET /v1/coupons/:id— Retrieve coupon:expand[]=applies_toshows its productsPOST /v1/coupons/:id— Update coupon:nameormetadata(other fields cannot change)DELETE /v1/coupons/:id— Delete coupon: discounts already made from it keep applyingGET /v1/coupons— List couponsPOST /v1/promotion_codes— Create promotion code: for a coupon (promotion[coupon], orcouponfor older clients) withcode(otherwise random),customer,expires_at,max_redemptions,active, andrestrictions(first_time_transaction,minimum_amountwithminimum_amount_currency)GET /v1/promotion_codes/:id— Retrieve promotion codePOST /v1/promotion_codes/:id— Update promotion code:active,metadataGET /v1/promotion_codes— List promotion codes: filter byactive,code,coupon,customer
discounts[0][coupon] or discounts[0][promotion_code] on a subscription, invoice, Checkout session, or preview. Customers take the older coupon and promotion_code params on create and update, and GET or DELETE /v1/customers/:id/discount reads or removes the customer’s discount. DELETE /v1/subscriptions/:id/discount removes a subscription’s.
Applying a coupon checks it as Stripe does: an unknown or deleted coupon returns resource_missing, one past redeem_by or max_redemptions returns coupon_expired, and an amount_off coupon in another currency is refused. A promotion code must be active, unexpired, under its max_redemptions, for its customer if it has one, and meet its restrictions: first_time_transaction refuses customers with an earlier payment or paid invoice, and minimum_amount refuses smaller orders. Each redemption raises times_redeemed on the coupon and the code.
How discounts bill:
- A subscription’s discounts apply to each of its invoices. Without its own discounts, a subscription uses the customer’s discount. Invoices take the subscription’s or customer’s discounts unless
discountsis given (discounts=""opts out). - Percentages come off the discountable lines;
amount_offcomes off up to their total. Several discounts apply in order. Each invoice line reports its share indiscount_amounts, and the invoice reportstotal_discount_amounts,discounts, andtotal. Proration lines and invoice items withdiscountable=falseare not discounted, andapplies_tolimits a coupon to its products. onceapplies to the first invoice with something to discount.repeatingapplies to invoices made withinduration_in_monthsof the discount’sstart(theend), so on a test clock a three-month coupon covers three monthly renewals.forevernever ends. A discount that has ended is removed when the next invoice finalizes, withcustomer.discount.deleted.- Previews (
create_preview) apply the same discounts and acceptdiscountsto try one out.
coupon.created, .updated, .deleted; promotion_code.created, .updated; customer.discount.created, .deleted.
Seed coupons and codes under coupons and promotion_codes in seed config, and tax rates under tax_rates.
Tax rates
POST /v1/tax_rates— Create tax rate:display_name,percentage,inclusive(all required),country,state,jurisdiction,tax_type,description,activeGET /v1/tax_rates/:id— Retrieve tax ratePOST /v1/tax_rates/:id— Update tax rate: anything butpercentageandinclusive;active: falsearchives itGET /v1/tax_rates— List tax rates: filter byactive,inclusive
tax_rates on invoice items, subscription items (items[i][tax_rates], add_invoice_items[i][tax_rates]), and Checkout line items, and default_tax_rates on invoices and subscriptions for lines without their own. A subscription’s invoices take its default_tax_rates. Unknown rates return resource_missing, and archived rates cannot be applied anew (they stay where they are used).
Taxes follow Stripe’s tax rate rules: each line is taxed on its amount after discounts. An exclusive rate adds its percentage of that amount, rounded per line. An inclusive rate is already in the amount: the taxable amount is the amount divided by one plus the inclusive rates, and the tax is the rest. Lines report taxes (amount, tax_behavior, tax_rate_details, taxable_amount), and invoices report total_taxes per rate, total_excluding_tax, and subtotal_excluding_tax; total and amount_due include exclusive tax. Previews and Checkout (total_details.amount_tax) use the same rules. Events: tax_rate.created, tax_rate.updated.
Invoices
POST /v1/invoices— Create invoice: a draft for acustomer.pending_invoice_items_behavior=includetakes the customer’s pending items (the default, as on Stripe, isexclude). Withsubscription, it takes only that subscription’s pending items; without it, only items not tied to a subscriptionPOST /v1/invoices/create_preview— Preview invoice: without making it (see Invoice previews)GET /v1/invoices/:id— Retrieve invoicePOST /v1/invoices/:id— Update invoice: a draft (description,metadata,auto_advance,collection_method,days_until_due,due_date,default_payment_method). Open invoices take onlymetadataandauto_advance; others onlymetadataDELETE /v1/invoices/:id— Delete draft invoice: its items go back to pendingPOST /v1/invoices/:id/finalize— Finalize invoice: a draftPOST /v1/invoices/:id/pay— Pay invoice: an open, uncollectible, or draft one (payment_method,off_session,paid_out_of_band)POST /v1/invoices/:id/send— Send invoice: asend_invoiceinvoice (finalizes a draft first)POST /v1/invoices/:id/void— Void invoice: an open or uncollectible onePOST /v1/invoices/:id/mark_uncollectible— Mark invoice uncollectible: an open oneGET /v1/invoices/:id/lines— List invoice lines: paginatedGET /v1/invoices— List invoices: filter bycustomer,subscription,status,collection_method,created
<customer invoice_prefix>-0001), applies the customer balance (credit lowers amount_due; a negative total becomes credit; starting_balance and ending_balance show the change), and creates the payment intent that collects it. An invoice with nothing to pay is paid at once. send_invoice invoices get due_date from days_until_due (30 days when unset).
Paying charges payment_method, else the invoice’s default_payment_method, else the customer’s invoice_settings.default_payment_method; with none, it returns 400. Payment follows the test cards: a decline returns the 402 card error, keeps the invoice open, raises attempt_count, and sends invoice.payment_failed. off_session defaults to true, so a card that needs authentication declines with authentication_required; with off_session=false it returns 402 invoice_payment_intent_requires_action and sends invoice.payment_action_required, and the invoice is paid once the 3D Secure page completes. paid_out_of_band=true marks it paid without a charge (amount_paid_off_stripe). Voiding cancels the payment intent and gives back the customer balance the invoice used.
To collect on the client, expand confirmation_secret and confirm its client_secret with Stripe.js. However the invoice’s payment intent succeeds, the invoice becomes paid and sends invoice.paid, invoice.payment_succeeded, and invoice_payment.paid. expand[]=payments lists the invoice payment. The payment intent cannot be canceled directly while the invoice is open; void the invoice instead.
Invoice items
POST /v1/invoiceitems— Create invoice item: for acustomer, priced byamount(negative for a credit),unit_amount_decimalwithquantity,pricing[price], orprice_data.invoiceputs it on a draft invoice; otherwise it waits as pendingGET /v1/invoiceitems/:id— Retrieve invoice itemPOST /v1/invoiceitems/:id— Update invoice item: amount, quantity, pricing, period, description, or metadataDELETE /v1/invoiceitems/:id— Delete invoice itemGET /v1/invoiceitems— List invoice items: filter bycustomer,invoice,pending
subscription on create ties a pending item to one of the customer’s subscriptions, so it is billed on that subscription’s next invoice. tax_rates taxes the item (see Tax rates); invoices take default_tax_rates for items without their own.
Items on a finalized invoice cannot change or be deleted (invoice_not_editable). price_data makes an inactive price, as on Stripe. The currency must match the invoice’s.
Automatic collection
Withauto_advance: true, invoices move on their own as billing time passes:
- A draft finalizes an hour after it was created (
automatically_finalizes_at), and acharge_automaticallyinvoice is charged off-session right away. - After a manual
finalize, the first charge is an hour later (next_payment_attempt). - A failed charge (including no payment method) sends
invoice.payment_failedand retries 3, 5, and 7 days after the previous attempt, four attempts in all. After the last,next_payment_attemptis null and the invoice stays open. The retry days can be changed in seed config (billing.retry_days). - A
send_invoiceinvoice sendsinvoice.overdueonce itsdue_datepasses. auto_advance: falseon an open invoice stops collection; setting it back schedules a charge an hour later.
/v1 request (so actions take that request’s time), and a test clock runs each action at the time it falls due while it advances. Events from automatic actions have request.id: null.
Invoice previews
POST /v1/invoices/create_preview returns the invoice Stripe would make, without creating anything. Previews use the same billing rules as the real invoices, so a preview and the invoice that follows it match. The ID starts with upcoming_in_, lines start with il_tmp_, and billing_reason is upcoming.
subscriptionalone previews the next renewal, with the pending items it would take.subscriptionwithsubscription_details(items,proration_behavior,proration_date,billing_cycle_anchor,trial_end,cancel_at,cancel_at_period_end,cancel_now) previews a change. Withalways_invoice, a new cycle, orcancel_now, it is the invoice made at once; otherwise it is the next renewal with the change’s prorations on it.customerwithsubscription_details[items]previews the first invoice of a new subscription.customeralone previews an invoice of the customer’s pending items.
invoice_upcoming_none, as Stripe does.
Credit notes
POST /v1/credit_notes— Create credit note: credit a finalizedinvoicebylinesoramount, withrefund_amount,credit_amount,out_of_band_amount,refunds,reason,memo,metadataGET /v1/credit_notes/preview— Preview credit note: the same, without issuing itGET /v1/credit_notes/preview/lines— Preview credit note linesGET /v1/credit_notes/:id— Retrieve credit noteGET /v1/credit_notes/:id/lines— List credit note linesPOST /v1/credit_notes/:id— Update credit note:memoormetadataPOST /v1/credit_notes/:id/void— Void credit noteGET /v1/credit_notes— List credit notes: filter byinvoice,customer
invoice_line_item credits part of an invoice line, by quantity or amount (all that is left when neither is given), with its share of the line’s discounts and the line’s tax rates. A custom_line_item credits unit_amount × quantity with its own tax_rates. A credit note cannot take more than is left of a line, or of the invoice total.
The total first comes off what is left to pay on an open invoice (pre_payment_amount): amount_due drops, the payment intent follows, and an invoice with nothing left is paid. The rest (post_payment_amount) must equal refund_amount (a refund of the invoice’s charge) plus credit_amount (customer balance credit, which the next invoice uses) plus out_of_band_amount plus linked refunds; otherwise it returns 400. The invoice adds them up in pre_payment_credit_notes_amount and post_payment_credit_notes_amount. Numbers follow the invoice’s (<invoice number>-CN-01).
Voiding gives back the balance credit and raises amount_due again. A credit note with a refund, or one that paid its invoice, cannot be voided. Events: credit_note.created, .updated, .voided.
Subscriptions
POST /v1/subscriptions— Create subscription: for acustomerwithitems(each a recurringpriceorprice_data, withquantity)GET /v1/subscriptions/:id— Retrieve subscriptionPOST /v1/subscriptions/:id— Update subscription: change items, quantities, trial, anchor, cancellation, collection, payment method, or metadataDELETE /v1/subscriptions/:id— Cancel subscription: now (invoice_now,prorate,cancellation_details)POST /v1/subscriptions/:id/pause— Pause subscription:type=subscriptionPOST /v1/subscriptions/:id/resume— Resume subscription: a paused one (billing_cycle_anchor,proration_behavior,payment_behavior)GET /v1/subscriptions— List subscriptions: filter bycustomer,price,status,collection_method,test_clock
billing_reason: subscription_create) is finalized and collected at once, according to payment_behavior:
allow_incomplete(the default) charges the default card. On success the subscription isactive; on a decline it staysincompletewith the invoice open.error_if_incompletereturns the card’s 402 error and creates nothing when the charge would fail.default_incompleteleaves the subscriptionincompleteso the client pays it: expandlatest_invoice.confirmation_secretand confirm it with Stripe.js.
incomplete subscription becomes active when its first invoice is paid. If it is not paid within 23 hours, it becomes incomplete_expired, its invoice is voided, and customer.subscription.deleted is sent. The first invoice is never retried. payment_settings[save_default_payment_method]=on_subscription saves the card that paid it as the subscription’s default.
The subscription’s card is its default_payment_method, else the customer’s invoice_settings.default_payment_method. collection_method=send_invoice (with days_until_due) sends invoices instead of charging them. add_invoice_items adds one-time prices to the first invoice. Pending invoice items for the customer go on the first invoice too.
Periods. Billing periods follow the price’s interval from billing_cycle_anchor. Month periods keep the anchor day and clamp to the month’s end (an anchor on 31 January renews on 28 February, then 31 March). A future billing_cycle_anchor bills a prorated first period up to it (proration_behavior=none makes it free). Each item reports current_period_start and current_period_end. Three days before each renewal, invoice.upcoming is sent with a preview of the invoice (not for send_invoice). At renewal, a draft invoice (billing_reason: subscription_cycle) is made with the subscription’s pending items, then finalized and charged an hour later.
Trials. trial_end (a timestamp) or trial_period_days starts a trialing subscription with a $0 invoice. customer.subscription.trial_will_end is sent three days before the trial ends (at once for shorter trials). When it ends, the first paid period starts. trial_settings[end_behavior][missing_payment_method] decides what happens without a card: create_invoice (the default) bills anyway, pause pauses the subscription, and cancel cancels it. An update with trial_end=now ends the trial at once; a later trial_end extends it.
Failed payments. A failed renewal makes the subscription past_due and retries on the invoice schedule. After the last retry the subscription is canceled, by default. billing.after_final_retry in seed config can make it unpaid (later invoices stay drafts) or leave it past_due. Paying or marking uncollectible the latest invoice makes the subscription active again.
Changes and proration. Updates take items (id to change or with deleted=true to remove; price alone to add), with proration_behavior:
create_prorations(the default) creates pending invoice items: a credit for the unused time on what was last billed and a charge for the remaining time on the new items, prorated to the second (proration_datesets the time). They are billed on the next invoice.always_invoiceinvoices the prorations at once and collects them.nonechanges the items without prorations.
billing_cycle_anchor=now, starts a new period at once, with credit for the unused time. During a trial, item changes cost nothing. Line descriptions follow Stripe’s (Unused time on 2 × Pro after 11 Jan 2026, Remaining time on Pro after 11 Jan 2026, 1 × Pro (at $20.00 / month)). billing_mode is flexible.
incomplete and paused subscriptions take no billing changes; canceled and expired ones take only metadata and cancellation_details.
Canceling. cancel_at_period_end=true or cancel_at (a timestamp, or min_period_end / max_period_end) schedules the end. A cancel_at within the period credits the time after it. DELETE cancels at once: prorate=true credits the unused time, invoice_now=true bills what is pending on a final invoice, and without either, pending prorations for the subscription are dropped. Canceling stops automatic collection on its open invoices and sends customer.subscription.deleted. Canceled subscriptions are left out of the list unless status=canceled, ended, or all is given.
Pausing. pause_collection[behavior] keeps the subscription running but changes its renewal invoices: keep_as_draft leaves them as drafts, mark_uncollectible and void finalize and then mark or void them. pause_collection[resumes_at] ends the pause at that time. /pause stops the subscription itself (paused, no invoices), and /resume restarts it, either keeping the cycle (billing_cycle_anchor=unchanged, billing the rest of the current period) or starting a new one now. With payment_behavior=resume_on_payment_success it resumes only once that invoice is paid. Both send customer.subscription.paused and customer.subscription.resumed.
Subscription items
POST /v1/subscription_items— Add subscription item: to asubscription(priceorprice_data,quantity,proration_behavior)GET /v1/subscription_items/:id— Retrieve subscription itemPOST /v1/subscription_items/:id— Update subscription item: change its price or quantityDELETE /v1/subscription_items/:id— Delete subscription item: a subscription keeps at least one itemGET /v1/subscription_items?subscription=— List subscription items: a subscription’s items
POST /v1/subscriptions/:id.
Test clocks
POST /v1/test_helpers/test_clocks— Create test clock: atfrozen_time(nameoptional;customermoves an existing customer onto it, and then the time cannot be in the past)GET /v1/test_helpers/test_clocks/:id— Retrieve test clockPOST /v1/test_helpers/test_clocks/:id/advance— Advance test clock: move it forward tofrozen_timeDELETE /v1/test_helpers/test_clocks/:id— Delete test clock: with its customers and their invoicesGET /v1/test_helpers/test_clocks— List test clocks
test_clock bill in the clock’s time: invoices and invoice items record it in created and date and carry test_clock. Advancing runs everything that falls due on the way, in order, each at its own time, so one advance across three weeks runs every payment retry. A clock only moves forward, at most two years per advance. When its customers have subscriptions, an advance can cover at most two billing periods of the shortest one, as on Stripe. The advance finishes before the response returns. The response still says status: "advancing", as Stripe’s does, and the clock reads ready from then on, so code that polls for ready works unchanged. Each advance sends test_helpers.test_clock.advancing and test_helpers.test_clock.ready. As on Stripe, unfiltered invoice and invoice item lists leave out clock customers’ objects; filter by customer to see them.
Checkout
POST /v1/checkout/sessions— Create Checkout sessionGET /v1/checkout/sessions/:id— Retrieve Checkout sessionPOST /v1/checkout/sessions/:id/expire— Expire Checkout sessionGET /v1/checkout/sessions— List Checkout sessions: filter bycustomer,status,payment_statusGET /checkout/:id— Hosted checkout page: HTMLPOST /checkout/:id/complete— Complete payment flow
mode=subscription takes recurring prices (one-time prices go on the first invoice) and subscription_data (trial_period_days, metadata). Completing the page creates the customer if there is none, saves the test card for off-session payments, and starts the subscription, so the session reports subscription and invoice. A session with a trial reports payment_status: no_payment_required. mode=payment refuses recurring prices.
discounts (one coupon or promotion code) applies a discount, or allow_promotion_codes=true shows a promotion code field on the hosted page (not both). The session reports amount_subtotal, amount_total, discounts, and total_details.amount_discount. A code typed on the page is checked like the API’s; an invalid one shows “This code is invalid.” In mode=subscription the discount goes on the subscription.
Events
GET /v1/events— List events: newest first. Filter bytype(exact, or a wildcard such ascustomer.*),types[](up to 20 exact types, not withtype),created(exact orgt,gte,lt,lte), anddelivery_successGET /v1/events/:id— Retrieve event
data.object as it was when the event happened. Use this to reconcile after a missed webhook. pending_webhooks counts the endpoints the event has not reached yet, so it drops when a retry succeeds; delivery_success=false lists events with a failed or pending delivery.
Webhooks
Events are delivered to webhook URLs from seed config (stripe.webhooks: url, events, optional secret, and connect: true for connected account events) and to endpoints created with /v1/webhook_endpoints.
Webhook endpoints
POST /v1/webhook_endpoints— Create webhook endpoint:urlandenabled_eventsrequired;description,metadata,api_versionoptionalGET /v1/webhook_endpoints/:id— Retrieve webhook endpointPOST /v1/webhook_endpoints/:id— Update webhook endpoint:url,enabled_events,description,metadata, ordisabledDELETE /v1/webhook_endpoints/:id— Delete webhook endpointGET /v1/webhook_endpoints— List webhook endpoints
whsec_... secret, the only time it is returned, and deliveries to that endpoint are signed with it. disabled: true pauses deliveries and sets status: "disabled". Webhooks from seed config are listed here too. enabled_events takes * or exact event types; http and https URLs, including localhost, are accepted.
Deliveries and event types
- Webhooks configured with a secret include
Stripe-Signature: t=<timestamp>,v1=<signature>, signed over<timestamp>.<raw body> - Each event is a full Stripe event object:
id(evt_...),object: "event",api_version(2026-09-30.endive),created,livemode: false,pending_webhooks,type,data.object, andrequestwith theidandidempotency_keyof the API call that caused it.*.updatedevents includedata.previous_attributeswith the old values of the fields that changed. customer.created,customer.updated,customer.deletedproduct.created,price.createdpayment_method.attached,payment_method.detached,payment_method.updatedsetup_intent.created,setup_intent.requires_action,setup_intent.succeeded,setup_intent.setup_failed,setup_intent.canceledpayment_intent.created,payment_intent.requires_action,payment_intent.amount_capturable_updated,payment_intent.succeeded,payment_intent.payment_failed,payment_intent.canceledcharge.succeeded(the full charge object) when a payment intent is confirmed,charge.failed,charge.captured,charge.refundedrefund.created,refund.updatedcharge.dispute.created,charge.dispute.updated,charge.dispute.closed,charge.dispute.funds_withdrawn,charge.dispute.funds_reinstatedaccount.updated(to Connect endpoints, withaccount),application_fee.created,transfer.created,transfer.updatedinvoiceitem.created,invoiceitem.deletedinvoice.created,invoice.updated,invoice.deleted,invoice.finalized,invoice.sent,invoice.paid,invoice.payment_succeeded,invoice.payment_failed,invoice.payment_action_required,invoice.voided,invoice.marked_uncollectible,invoice_payment.paidinvoice.overduewhen asend_invoiceinvoice passes its due dateinvoice.upcomingthree days before a subscription renewscustomer.subscription.created,customer.subscription.updated,customer.subscription.deleted(when a subscription is canceled or expires),customer.subscription.trial_will_end,customer.subscription.paused,customer.subscription.resumedtest_helpers.test_clock.created,test_helpers.test_clock.advancing,test_helpers.test_clock.ready,test_helpers.test_clock.deletedcustomer.updatedalso fires when finalizing or voiding an invoice changes the customerbalancecheckout.session.completedwhen a checkout session is completedcheckout.session.expiredwhen a checkout session expires
Seed config
recurring: optional; omit for a one-time pricecoupons: optional; percent_off or amount_off (with currency)duration: once (the default), repeating, or foreverpromotion_codes: optional; customer-facing codes for seeded couponsfirst_time_transaction: optional restrictionstax_rates: optional; id, display_name, percentage, inclusiveaccounts: optional; connected accounts, onboarded unless onboarded: falsetype: standard (the default), express, or custombilling: optional; Stripe’s automatic collection settingsretry_days: days between payment retries (the default)after_final_retry: subscription after the last retry: cancel, unpaid, or past_due
Limits
- Connect: transfer reversals, refunds of application fees (
refund_application_fee,reverse_transfer), payouts, persons and external account objects, and automatic billing (auto_advance, wall clock renewals) for connected accounts outside their own requests. - Coupons:
discountson single invoice items or invoice lines (400parameter_unknown) andcurrency_options. - Tax: Stripe Tax (
automatic_tax[enabled]=truereturns 400) and flat amount rates. - Credit notes:
shipping_costand linetax_amounts. - Invoices: hosted invoice pages and PDFs (
hosted_invoice_urlandinvoice_pdfare null). - Subscriptions: Stripe Tax, metered and tiered prices, subscription schedules, billing thresholds,
pending_if_incomplete,trial_from_plan, andbackdate_start_date. They return 400parameter_unknownrather than being ignored.
Conformance
See CONFORMANCE.md for every provider.