Billtap is a fixture-backed billing lab for local development and CI. Its goal is measurable Stripe API compatibility for a growing, documented subset, plus local checkout, portal, webhook, and scenario tooling. It is not a payment processor or a Stripe Dashboard replacement.
The compatibility promise is scoped but parity-driven:
- Stripe-compatible request validation and response shapes for documented endpoints, backed by tests and scorecard cases.
- Hosted sandbox checkout and portal flows for exercising app integration code.
- Webhook delivery evidence for retries, duplicate delivery, delay, out-of-order delivery, and replay.
- Scenario and fixture APIs that make the supported subset repeatable.
Anything outside this document should be treated as unsupported until it has a fixture, a test, and an explicit compatibility note.
Known Stripe OpenAPI routes that are not implemented by Billtap first run
OpenAPI-derived parameter validation. Malformed requests return Stripe-shaped
parameter_unknown, parameter_missing, or parameter_invalid errors; valid
but unimplemented requests return unsupported_endpoint instead of silently
approximating provider behavior. When diagnostics are enabled, the request trace
captures that error code and the original path so agents can distinguish bad
test setup, unsupported coverage gaps, missing local data, and webhook failures.
| Level | Meaning |
|---|---|
| Supported | Implemented in the local runtime and covered by automated tests or release-blocking compatibility scorecard cases. Examples and fixtures are supporting evidence, not the sole basis for a supported claim. |
| Billtap-specific | Public Billtap API, not intended to match Stripe. |
| Partial | Useful for smoke tests, but not a full provider behavior model. |
| Unsupported | Not implemented, not claimed, or intentionally out of scope. |
The offline compatibility scorecard can be generated without external services:
go run ./cmd/billtap compatibility scorecard --output-dir dist/compatibilityIt writes:
compatibility-scorecard.jsoncompatibility-scorecard.mdreplay-bundles/*.jsonfor anymismatchorerrorcase
Scorecard statuses are:
imported: case ran against Billtap and matched the normalized expectation.skipped: case is in the corpus but intentionally not run by the offline lane.unsupported: case documents unsupported behavior from this compatibility contract.mismatch: case ran but normalized actual behavior differed from expectation.error: the scorecard runner or Billtap returned an unexpected internal error.
Current public-readiness corpus:
- Scorecard version:
l3-public-readiness-v7 - Release-blocking cases: 49
- Covered categories: request validation, protocol parameter acceptance, OpenAPI-backed fallback validation, idempotency mismatch, deterministic checkout payment-error aliases, and direct PaymentIntent/SetupIntent state transitions, invoice retry/payment mutation, and local clock-driven renewal and period-end cancellation scenarios
- Required release result:
mismatch=0,error=0, andpassed=true
The scorecard is the release contract for Billtap's documented compatibility subset. Broader Stripe API parity is expanded incrementally through the roadmap instead of being implied before a surface has tests and fixtures.
Broader Stripe API compatibility work is tracked separately in
STRIPE_API_COMPATIBILITY_ROADMAP.md. New endpoint families should move through
inventory, schema validation, fixture response, stateful local behavior,
scenario coverage, webhook modeling, and SDK smoke levels before they become
public claims.
The generated Stripe API inventory also reports
summary.schema_validated_operations, which counts operations from the input
OpenAPI file that expose parameter or request-body schemas and also match
Billtap's bundled OpenAPI-derived validation catalog. This is a diagnostic and
planning metric only. It does not increase summary.implemented_operations; an
endpoint still needs an explicit runtime claim, tests, fixtures, and
documentation before it counts as implemented.
Base path: /v1. A parallel test run can scope the same Stripe-like API under
/runs/<runId>/v1; unscoped requests use the backward-compatible default
run. When PUBLIC_BASE_PATH or BILLTAP_PUBLIC_BASE_PATH is set, or a proxy
sends X-Forwarded-Prefix, the same API is available below that browser-facing
prefix, such as /billtap/v1 or /billtap/runs/<runId>/v1. Internal service
traffic can keep using the unprefixed service URL.
| Resource | Endpoints | Level | Scope |
|---|---|---|---|
| Customers | POST /v1/customers, GET /v1/customers, GET /v1/customers/search, GET /v1/customers/{id}, POST /v1/customers/{id} |
Supported | Create, list, retrieve, update, and search email, name, and metadata. List supports email and limit filters. Search supports a measured Stripe Search Query Language subset for id, email, name, and metadata equality clauses joined by AND. |
| Products | POST /v1/products, GET /v1/products, GET /v1/products/{id}, POST /v1/products/{id}, DELETE /v1/products/{id} |
Supported | Create, list, retrieve, update, and delete local service products with metadata. Deletion removes the product; existing prices keep referencing the deleted product id. |
| Product search | GET /v1/products/search |
Partial | Supports metadata equality filters such as metadata['tenantId']:'saas' and active:'true'. This is not Stripe Search Query Language parity. |
| Prices | POST /v1/prices, GET /v1/prices, GET /v1/prices/{id}, POST /v1/prices/{id}, GET /v1/prices/search |
Supported | Create, list, retrieve, update, and search prices. Update accepts transfer_lookup_key to move a key another price holds. Supports product, currency, unit_amount, lookup_key, recurring interval fields, active, and metadata. List supports product, active, type=recurring, and type=one_time. Search supports a measured Stripe Search Query Language subset for active, type, lookup_key, and metadata equality clauses joined by AND, including one-time price lookup by lookup_key. |
| Coupons | POST /v1/coupons, GET /v1/coupons, GET /v1/coupons/{id}, POST /v1/coupons/{id}, DELETE /v1/coupons/{id} |
Partial | Create/list/retrieve local coupon evidence for SDK and fixture smoke paths. Amount-off, percent-off, duration, metadata, and deletion markers are modeled. A bounded single-discount subset applies coupons to customer defaults, checkout sessions, subscriptions, invoice preview math, and renewal invoices; redemption limits and expiration analytics are not modeled. |
| Promotion codes | POST /v1/promotion_codes, GET /v1/promotion_codes, GET /v1/promotion_codes/{id}, POST /v1/promotion_codes/{id} |
Partial | Create/list/retrieve local promotion-code evidence linked to a local coupon. List supports code, coupon, customer, and active filters. Promotion codes can be applied through discounts[0][promotion_code]; minimum amounts, expiration rules, and promotion analytics are not modeled. |
| Tax rates | GET/POST /v1/tax_rates, GET/POST /v1/tax_rates/{id} |
Partial | Create/list/retrieve/update local tax-rate evidence. Checkout subscription_data[default_tax_rates][] and subscription default_tax_rates resolve txr_* IDs and snapshot inclusive/exclusive rates onto sessions, subscriptions, invoices, renewal/proration math, and previews, applied after discounts; automatic_tax is mutually exclusive. Fixture packs can seed tax rates with explicit IDs. Jurisdiction and address-based calculation are not modeled; automatic_tax remains a customer-metadata tax_percent simulation. |
| Customer tax IDs | GET/POST /v1/customers/{id}/tax_ids, GET/DELETE /v1/customers/{id}/tax_ids/{id} |
Partial | Local customer tax-ID evidence store backing checkout tax_id_collection[enabled] flows. Provider verification is not modeled. |
| Checkout sessions | POST /v1/checkout/sessions, GET /v1/checkout/sessions, GET /v1/checkout/sessions/{id}, POST /v1/checkout/sessions/{id}, POST /v1/checkout/sessions/{id}/expire, GET /v1/checkout/sessions/{id}/line_items |
Supported | Creates subscription- and payment-mode sandbox checkout sessions from request line items and hosted Billtap URLs; setup mode is rejected. The Stripe-style session response leaves line_items unexpanded; GET .../line_items returns them as expanded item objects with pre-discount amounts (per-line discount/tax splits are not modeled). Accepts Stripe SDK form params allow_promotion_codes, discounts[0][coupon], discounts[0][promotion_code], subscription_data[trial_period_days], and subscription_data[default_tax_rates] (applied to session totals; exclusive/inclusive snapshots; mutually exclusive with automatic_tax); trial checkout creates local trialing subscription evidence. Payment-mode sessions accept inline price_data line items (creating local product/price evidence), payment_intent_data[...], client_reference_id, session-level metadata[...], automatic_tax[enabled], and tax_id_collection[enabled] (tax snapshot from customer metadata tax_percent), and complete into a one-time PaymentIntent without subscription or invoice evidence. POST .../sessions/{id} updates an open session (metadata merge plus line_items[N][quantity] overrides with immutable prices); expire moves an open session to expired and emits checkout.session.expired; non-open sessions return invalid_request_error. Hosted URLs use the request host by default, or BILLTAP_PUBLIC_BASE_URL when configured for container-to-host browser flows. PUBLIC_BASE_PATH, BILLTAP_PUBLIC_BASE_PATH, and X-Forwarded-Prefix are reflected in hosted browser URLs. |
| Checkout completion | POST /v1/checkout/sessions/{id}/complete, POST /api/checkout/sessions/{id}/complete, POST/DELETE /api/checkout/sessions/{id}/promotion_code |
Billtap-specific | Completes a sandbox checkout and creates subscription, invoice, payment intent, timeline, and checkout webhook evidence. Supports success plus deterministic failure aliases such as card_declined, insufficient_funds, expired_card, incorrect_cvc, processing_error, authentication_required, payment_pending, canceled, and documented Stripe test PaymentMethod IDs such as pm_card_visa_chargeDeclined. Hosted checkout can apply or remove a promotion code on an open allow_promotion_codes session via POST/DELETE /api/checkout/sessions/{id}/promotion_code (form promotion_code=<code>), refreshing session totals before completion. |
| Billing portal sessions | POST /v1/billing_portal/sessions |
Partial | Returns a Stripe-like billing_portal.session object and Billtap portal URL for a known customer, including flow, locale, on_behalf_of, return_url, and livemode fields. Accepts customer, return_url, optional configuration, locale, on_behalf_of, and flow_data, with enum and required nested-field validation for the modeled portal flows. The hosted portal can simulate saved payment-method updates and subscription cancellation, then redirect to return_url; full Stripe-hosted portal configuration rendering is not modeled. |
| Billing portal configurations | GET/POST /v1/billing_portal/configurations, GET/POST /v1/billing_portal/configurations/{id} |
Partial | Local portal-configuration evidence with Stripe-shaped business_profile, default_return_url, login_page, features[...], and metadata. Create applies Stripe-shaped feature defaults; update merges business profile, features, and metadata and bumps updated. List filters by active and is_default; the first configuration in a run becomes the default. The hosted portal does not render configuration features. |
| Subscriptions | POST /v1/subscriptions, GET /v1/subscriptions, GET /v1/subscriptions/search, GET /v1/subscriptions/{id}, POST /v1/subscriptions/{id}, DELETE /v1/subscriptions/{id}, POST /v1/subscriptions/{id}/resume, POST /v1/subscriptions/{id}/migrate (billing-mode evidence), nested GET/POST/DELETE /v1/customers/{id}/subscriptions..., GET/DELETE /v1/subscriptions/{id}/discount |
Partial | Create/list/retrieve subscriptions through the local checkout-completion state path. Update supports item replacement, metadata merge, single coupon or promotion-code discount application (discount objects serialize the Stripe SDK 31 source.{coupon,promotion_code} shape alongside the legacy top-level coupon), test_clock, cancel_at_period_end, pause_collection, and accepted billing/proration evidence including proration_date, proration_behavior, payment_behavior, billing_cycle_anchor, and trial_end; resume clears local pause evidence and emits customer.subscription.updated. Top-level and nested customer lists support status and metadata equality filters such as metadata[billtap_fixture_ref]. Search supports id, customer, status, and metadata equality clauses joined by AND. Delete performs immediate sandbox cancellation. Test-clock and scenario clock advances can activate due trials, renew active periods with bounded discount math, fail configured renewals from subscription or customer metadata, and cancel period-end subscriptions in the local billing graph. |
| Subscription schedules | POST /v1/subscription_schedules, GET /v1/subscription_schedules, GET /v1/subscription_schedules/{id}, POST /v1/subscription_schedules/{id}, POST /v1/subscription_schedules/{id}/cancel, POST /v1/subscription_schedules/{id}/release |
Partial | Local schedule evidence for one due phase on an existing subscription. Test-clock advance applies a due phase by replacing subscription items and emits customer.subscription.updated; complex multi-phase billing, proration, invoices, and schedule lifecycle parity are not modeled. |
| Subscription items | POST /v1/subscription_items, GET /v1/subscription_items, GET /v1/subscription_items/{id}, POST /v1/subscription_items/{id}, DELETE /v1/subscription_items/{id} |
Partial | List, retrieve, add, update, and remove local subscription items. List supports the subscription filter; update replaces price/quantity and echoes metadata and evidence-only tax_rates. Create, update, and delete accept proration_behavior (none/create_prorations/always_invoice) and proration_date through the same proration path as subscription update, applying the subscription's default_tax_rates after discounts: always_invoice issues a paid subscription_update invoice and repoints latest_invoice, and create_prorations defers the delta to the next renewal invoice. Deleting the last item is rejected. Item IDs are stored at creation so deletes no longer shift later IDs. Item-level tax_rates are evidence-only, and metered usage is not modeled. |
| Invoices | POST /v1/invoices, GET /v1/invoices, GET /v1/invoices/search, GET /v1/invoices/{id}, POST /v1/invoices/{id}, DELETE /v1/invoices/{id}, POST /v1/invoices/{id}/finalize, POST /v1/invoices/{id}/pay, POST /v1/invoices/{id}/send, POST /v1/invoices/{id}/void, POST /v1/invoices/{id}/mark_uncollectible, POST /v1/invoices/{id}/attach_payment, POST /v1/invoices/{id}/add_lines, POST /v1/invoices/{id}/update_lines, POST /v1/invoices/{id}/remove_lines, POST /v1/invoices/{id}/lines/{line_item_id}, GET /v1/invoices/{id}/lines, GET /v1/invoices/{id}/payments, POST /v1/invoices/create_preview, GET/POST /v1/invoices/upcoming |
Partial | List/retrieve/search invoices created by checkout and create a bounded manual one-time invoice flow. Search supports id, customer, subscription, and status clauses joined by AND. pay retries open checkout invoices with deterministic sandbox payment_method or source aliases, mutating invoice, subscription, payment-intent, timeline, and webhook evidence. Manual invoices accept customer, currency, collection_method, default_payment_method, description, auto_advance=false, pending_invoice_items_behavior, payment_settings[payment_method_types], and metadata[...]; pending_invoice_items_behavior=include attaches the customer's same-currency pending invoice items once at create. Draft invoices can be updated in place (POST /v1/invoices/{id}: description, days_until_due, default_payment_method, metadata merge) and deleted with their attached lines and timeline evidence (DELETE); add_lines/update_lines/remove_lines (and the single-line POST .../lines/{line_item_id}) mutate draft lines through the invoice-item path and recompute subtotal/total/amount_due; attach_payment records a customer-matched PaymentIntent or payment record on a draft as evidence while collection still runs through finalize/pay. finalize creates local PaymentIntent evidence, and pay applies per-intent or customer-level default outcomes including success, card decline, and requires_action. send records local email evidence and emits invoice.sent; void moves an open invoice to void and emits invoice.voided; mark_uncollectible moves an open invoice to uncollectible and emits invoice.marked_uncollectible; non-open statuses return invalid_request_error. Invoice responses include confirmation_secret, local hosted/PDF paths, payments.data.payment.payment_intent, metadata, status transitions, automatic tax state, tax totals, payment settings, array-shaped discounts, lines.data serialized like the lines endpoint, and post_payment_credit_notes_amount on paid invoices. Preview endpoints calculate a Stripe-shaped subscription-update proration line from current period bounds, old/new price totals, subscription_details[items], subscription_details[proration_date], subscription_details[proration_behavior], and subscription_details[billing_cycle_anchor]; previews without item overrides return the subscription's next billing cycle (items, discounts, and tax with billing_reason: upcoming) matching the next renewal invoice. A bounded single-discount subset updates subtotal, total, and total_discount_amounts; invoice rendering, automatic collection, and full dunning lifecycle are outside the modeled subset. |
| Invoice items | POST /v1/invoiceitems, GET /v1/invoiceitems, GET /v1/invoiceitems/{id} |
Partial | Creates, lists, and retrieves local invoice item evidence for manual one-time invoices. customer, invoice, amount, currency, description, and metadata[...] are accepted, or pricing[price] plus quantity instead of amount (line amount is the price's unit_amount times quantity, currency from the price). Omitting invoice stores the item as a pending customer item that POST /v1/invoices with pending_invoice_items_behavior=include attaches; subscription is accepted and echoed. Tax, discount, and full line mutation parity are not modeled. |
| Payment intents | POST /v1/payment_intents, GET /v1/payment_intents, GET /v1/payment_intents/search, GET /v1/payment_intents/{id}, POST /v1/payment_intents/{id} (metadata/description update), POST /v1/payment_intents/{id}/confirm, POST /v1/payment_intents/{id}/capture, POST /v1/payment_intents/{id}/cancel |
Partial | Create/list/retrieve/search and mutate local payment intents. Search supports id, customer, status, and metadata equality clauses joined by AND. confirm supports deterministic sandbox PaymentMethod aliases such as pm_card_visa, pm_card_visa_chargeDeclined, pm_card_threeDSecure2Required, requires_action, and local bank-transfer processing. Manual capture moves through requires_capture before capture succeeds. Direct one-time intents emit payment_intent.created and a terminal event when confirmed. Deferred per-intent outcomes and customer defaults are supported; requires_action can return either use_stripe_sdk or local redirect_to_url next-action payloads. |
| Setup intents | POST /v1/setup_intents, GET /v1/setup_intents, GET /v1/setup_intents/{id}, POST /v1/setup_intents/{id}/confirm, POST /v1/setup_intents/{id}/cancel |
Partial | Create/list/retrieve and mutate local setup intents with deterministic success, decline, and authentication-required aliases. Mandates, bank-account verification, and full SCA behavior are not modeled. |
| Payment methods | GET/POST /v1/payment_methods, GET/POST /v1/payment_methods/{id}, POST /v1/payment_methods/{id}/attach, POST /v1/payment_methods/{id}/detach, GET /v1/customers/{id}/payment_methods?type=card, GET /v1/customers/{id}/payment_methods/{id} |
Partial | Returns deterministic sandbox card PaymentMethod projections for known customers, including SDK-friendly billing_details, card.checks, country, fingerprint, funding, networks, three_d_secure_usage, metadata, and redaction fields. Query validation covers type, allow_redisplay, limit, and unknown parameters; non-card valid types return an empty local list. Customer fixtures can opt into empty or explicit payment-method lists for no-card and multi-card local scenarios. Create/attach/detach mutate local customer payment-method metadata and reject real card data; general update is accepted as a compatibility no-op for projected local cards. |
| Customer cash balance | GET /v1/customers/{id}/cash_balance, POST /v1/customers/{id}/cash_balance, GET /v1/customers/{id}/cash_balance_transactions, GET /v1/customers/{id}/cash_balance_transactions/{id}, POST /v1/test_helpers/customers/{id}/fund_cash_balance |
Partial | Local cash-balance evidence for bank-transfer smoke tests. The test-helper funding endpoint records a cash-balance transaction and settles processing bank-transfer PaymentIntents for that customer. Banking rails, reconciliation, currency conversion, and customer-balance invoice application are not modeled. |
| Connect platform evidence | GET /v1/account, POST /v1/accounts, GET /v1/accounts, GET /v1/accounts/{id}, POST /v1/accounts/{id}, DELETE /v1/accounts/{id}, POST /v1/account_links, POST /v1/account_sessions, account capabilities, people/persons, external accounts, transfers/reversals, payouts, application fees/refunds |
Partial | Persist local connected-account profiles, capability status, person evidence, bank-account evidence, transfers, transfer reversals, payouts, and application-fee refunds. Account links, account sessions, and login links return local URLs/client secrets. Account deletion returns a local deletion marker. Request traces preserve Stripe-Account routing evidence, and local Connect evidence can emit transfer.*, payout.*, and application_fee.refunded webhooks. KYC, identity verification, bank verification, real onboarding, balance movement, account closure, and settlement behavior are not modeled. |
| Refunds | POST /v1/refunds, GET /v1/refunds, GET/POST /v1/refunds/{id}, POST /v1/refunds/{id}/cancel |
Partial | Create/list/retrieve/update local refund evidence against an invoice, payment intent, or charge-like ID. Omitting amount refunds the full remaining refundable balance on the invoice or payment intent; requested amounts cannot exceed that balance. Creation emits charge.refunded and charge.refund.updated; status updates and test-clock settlement emit charge.refund.updated. Refund fixtures can seed pending and settle on settle_at/available_on. Balance transactions and processor accounting are outside the modeled subset. |
| Credit notes | POST /v1/credit_notes, GET /v1/credit_notes, GET /v1/credit_notes/{id}, GET /v1/credit_notes/{id}/lines, POST /v1/credit_notes/{id}/void |
Partial | Create/list/retrieve/void local credit note evidence for an invoice and emit credit_note.created / credit_note.voided. memo, out_of_band_amount, and refund_amount are accepted, persisted, and echoed with derived credit_amount; out_of_band_amount is external settlement and does not change customer cash balance. GET .../lines returns one line derived from the stored amount and memo/reason. Line-level tax, discount, PDF, and customer-balance math are outside the modeled subset. |
| Disputes | GET /v1/disputes, GET /v1/disputes/{id}, POST /v1/disputes/{id}, POST /v1/disputes/{id}/close, GET /v1/charges/{id}/dispute, POST /v1/charges/{id}/dispute |
Partial | Create, retrieve, list, update evidence, and close local dispute evidence for chargeback-style tests. Fixture seeding and APIs emit charge.dispute.created, charge.dispute.updated, charge.dispute.funds_withdrawn, and charge.dispute.closed where applicable. Representment deadlines, balance movements, and processor outcomes are outside the modeled subset. |
| Test clocks | POST /v1/test_helpers/test_clocks, GET /v1/test_helpers/test_clocks, GET /v1/test_helpers/test_clocks/{id}, DELETE /v1/test_helpers/test_clocks/{id}, POST /v1/test_helpers/test_clocks/{id}/advance |
Partial | Create/retrieve/list/advance persisted local clocks. Customers, subscriptions, and pending refunds can be attached with test_clock; advancing a clock processes attached trial activation, renewals, configured renewal failures, period-end cancellation, and refund settlement. DELETE removes the clock and detaches (rather than deletes) referenced customers and subscriptions, and re-applying a fixture pack recreates a clock that ran past the pack's declared frozen_time so the pack's premise is restored. |
| Webhook endpoints | POST /v1/webhook_endpoints, GET /v1/webhook_endpoints, GET /v1/webhook_endpoints/{id}, POST /v1/webhook_endpoints/{id}, PATCH /v1/webhook_endpoints/{id}, DELETE /v1/webhook_endpoints/{id}, GET /v1/webhook_endpoints/{id}/attempts |
Supported | Manage local webhook endpoints and inspect endpoint-scoped delivery attempts. Secrets are generated when omitted and masked in API responses. enabled_events supports exact event names, *, and prefix wildcards such as invoice.*. PATCH accepts the same local mutable fields as POST, including the enabled alias for active. |
| Events | GET /v1/events, GET /v1/events/{id} |
Supported | List and retrieve Billtap-created events. Filters include type, scenarioRunId, created[gte], created[gt], created[lte], created[lt], data.object.customer, and data.object.metadata[key]. |
All list and search endpoints are scoped by the selected run. Webhook endpoints
registered through /runs/<runId>/v1/webhook_endpoints receive only events
emitted in that run, and local test clocks are isolated the same way.
Base path: /api
| Area | Endpoints | Scope |
|---|---|---|
| Checkout | POST /api/checkout/sessions/{id}/complete |
Billtap-only checkout completion endpoint used by the hosted checkout UI and local tests. |
| Portal | GET /api/portal, GET /api/portal/customers/{id}, POST /api/portal/subscriptions/{id}/plan-change, POST /api/portal/subscriptions/{id}/seat-change, POST /api/portal/subscriptions/{id}/cancel, POST /api/portal/subscriptions/{id}/resume, POST /api/portal/customers/{id}/payment-method |
Sandbox portal state and actions. These update local billing state and timeline evidence; they do not claim Stripe Billing Portal parity. |
| Dashboard evidence | GET /api/objects, GET /api/timeline, GET /api/delivery-attempts, POST /api/debug-bundles |
Object lists, timelines, delivery evidence, and debug bundle data for local investigation. Timeline filters accept both object-specific IDs and generic object_type/object_id. |
| Webhook operations | POST /api/events/{id}/replay, POST /api/events/replay-group, POST /api/webhooks/endpoints/{id}/replay-historical |
Replays one event or a group of events and can schedule duplicate, delayed, out-of-order, signature-mismatch, simulated endpoint response, and fail-first-then-deliver attempts. Endpoint-scoped historical replay catches up matching events that were emitted before an app registered its webhook endpoint. |
| Payment intent actions | POST /api/payment_intents/{id}/complete_action, POST /api/payment_intents/{id}/cancel_action, POST /api/payment_intents/{id}/outcome |
Billtap-only callbacks used by hosted/client smoke tests to complete or abandon a local requires_action PaymentIntent, or to attach a deferred local outcome before confirming a one-time PaymentIntent. |
| Dispute simulation | POST /api/disputes |
Billtap-only shortcut for creating local dispute evidence when an app does not already have a charge-like ID. |
| Fixtures | POST /api/fixtures/apply, POST /api/fixtures/validate, GET /api/fixtures/resolve, GET /api/fixtures/snapshot, POST /api/fixtures/assert |
Data-driven setup and dry-run validation APIs for customers, connected accounts, products, prices, test clocks, subscription graphs, invoices, payment intents, refunds, credit notes, disputes, and timeline evidence. |
| Scenarios | POST /api/scenarios/run |
Runs a scenario JSON object or YAML payload and returns the scenario report. |
| Boundary controls | GET /api/audit-log, POST /api/retention/apply |
Audit and retention controls for replay, delivery overrides, and raw evidence redaction. |
Billtap-only /api endpoints are also available under /runs/<runId>/api for
run-scoped checkout completion, fixture apply/snapshot/assert, diagnostics, and
webhook replay workflows.
Billtap emits Stripe-style event envelopes for the supported checkout sequence
and stores delivery attempts for local debugging. The envelope includes event
IDs, event type, created time, livemode: false, data.object, request
metadata, and Billtap metadata.
Supported generic event types:
checkout.session.completedcheckout.session.expiredcustomer.updatedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedpayment_method.attachedinvoice.createdinvoice.finalizedinvoice.payment_succeededinvoice.paidinvoice.payment_failedinvoice.voidedpayment_intent.createdpayment_intent.succeededpayment_intent.processingpayment_intent.canceledpayment_intent.payment_failedpayment_intent.requires_actionpayment_intent.amount_capturable_updatedcharge.refundedcharge.refund.updatedcharge.dispute.createdcharge.dispute.updatedcharge.dispute.funds_withdrawncharge.dispute.closedcredit_note.createdcredit_note.voidedsetup_intent.createdsetup_intent.succeededsetup_intent.canceledsetup_intent.setup_failedsetup_intent.requires_actiontransfer.createdtransfer.reversedpayout.createdpayout.canceledpayout.reversedapplication_fee.refunded
Current event boundaries:
- Checkout completion emits the generic checkout, subscription, invoice, and
payment-intent sequence. Async-pending checkout emits
payment_intent.processingwithout an invoice failure event; canceled checkout emitscheckout.session.expired,payment_intent.canceled, andinvoice.voided. - Direct PaymentIntent and SetupIntent APIs emit local intent events for create, confirm, capture, cancel, failure, processing, and requires-action states. Billtap action callbacks can complete or cancel a local SCA-required intent. Cash-balance funding can settle local bank-transfer PaymentIntents for the same customer. These events are for local webhook/debug evidence and do not imply real payment processing.
- Portal subscription actions update local billing state and timeline evidence,
and enqueue
customer.subscription.updatedorcustomer.subscription.deletedwhen the subscription changes. Portal payment method simulation saves a deterministic default payment method and emitspayment_method.attachedpluscustomer.updated. This remains local portal evidence and does not claim Stripe Billing Portal parity. - Test-clock advance emits the local subscription, invoice, payment-intent,
refund, credit-note, or subscription-schedule event evidence created by the
processed mutation. It does not model Stripe's asynchronous clock advancement
lifecycle beyond
ready/advancingevidence. - Refund and credit-note APIs emit local payment-history events for application webhook and history screens. They do not imply balance transaction, settlement, dispute, or ledger parity.
- Dispute APIs emit local chargeback-style evidence only. They do not imply representment, deadline, balance movement, or processor outcome parity.
- Connect evidence APIs emit local transfer, payout, and application-fee refund events with request traces that preserve connected-account routing context. People/persons and account deletion are local evidence only. They do not imply real balance movement, onboarding, KYC, identity verification, bank verification, provider-side account closure, or settlement behavior.
- Replay keeps the original event ID and payload, then creates new delivery attempts with replay metadata.
- Historical endpoint replay uses
POST /api/webhooks/endpoints/{id}/replay-historical, respects the endpoint'senabled_eventsfilters, defaultsuntilto endpoint creation time, skips already-delivered event/endpoint pairs unlessforce=true, and marks delivery attempts with historical replay metadata. - Duplicate delivery reuses the event ID and payload.
- Delay changes delivery scheduling, not event creation time.
- Out-of-order delivery changes attempt ordering evidence, not canonical event sequence.
- Group replay can reorder, delay, omit, or signature-mismatch a selected set of existing event IDs so application handlers can be tested against multi-event delivery drift without changing canonical event history.
Delivery headers use:
Billtap-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256>
The default header name is Billtap-specific. Set
BILLTAP_WEBHOOK_SIGNATURE_HEADER=Stripe-Signature when an application already
verifies Stripe's standard webhook header and should consume Billtap through the
same receiver path.
Webhook envelopes emit a Stripe API version in api_version so Stripe SDK
webhook deserializers can hydrate data.object into typed models. The default
is 2025-12-15.clover; set BILLTAP_WEBHOOK_API_VERSION when the application
under test pins a different Stripe SDK/API version.
Billtap scenarios are deterministic YAML/JSON flows for local and CI tests.
Supported generic actions:
customer.createproduct.createprice.createcheckout.createcheckout.completecheckout.cancelsubscription.updatesubscription.cancelsubscription.resumeclock.advanceinvoice.fail_paymentinvoice.retrywebhook.replaywebhook.deliver_duplicatewebhook.deliver_out_of_orderapp.assert
Supported SaaS profile actions are documented in
specs/000-product/contracts/scenario.md and exercised by
examples/saas-adoption-contract.yml.
Current scenario boundaries:
checkout.completemutates local billing state and emits checkout-related webhook evidence.checkout.cancelis a deterministic local cancellation outcome over the checkout completion path. It records an expired checkout session, a canceled payment intent, a void invoice, and checkout expiration webhook evidence.subscription.updatesupportscancel_at_period_endfor local lifecycle scenarios.subscription.canceluses the same local portal cancellation state machine for period-end or immediate cancellation, andsubscription.resumeclears pending cancellation.invoice.fail_paymentuses the same local invoice payment mutation asinvoice.retry, but defaults to a declined-card outcome when no explicit failure alias is supplied.invoice.retrycalls the same local invoice payment mutation asPOST /v1/invoices/{id}/paywhen a billing service and invoice reference are available. Success marks the invoice paid, clearsnext_payment_attempt, succeeds the payment intent, and reactivates the subscription. Declines keep the invoice open, incrementattempt_count, set the next retry time, and update the subscription topast_due. SaaS profile-only scenarios that run without a billing invoice still record deterministic evidence only.clock.advanceadvances scenario time and asks the local billing service to process due active/trialing subscriptions. It activates due trials, creates paid renewal invoice/payment-intent evidence, creates configured failed renewal evidence, or cancels subscriptions scheduled withcancel_at_period_end. Stripe-like test-clock APIs expose the same bounded local clock engine for fixture-backed integration tests.- Generic webhook replay is available through
POST /api/events/{id}/replayand thewebhook.replayscenario action. Replay can schedule duplicate, delayed, out-of-order, simulated endpoint status, timeout, generic transport error, signature-mismatch, and fail-first-then-deliver attempts. Endpoint catchup replay is available throughPOST /api/webhooks/endpoints/{id}/replay-historicalfor fixture events emitted before an application registered its webhook endpoint. webhook.deliver_duplicateandwebhook.deliver_out_of_orderuse generic replay delivery when they reference a Billtap event and a webhook service is configured. They still update SaaS profile webhook evidence when they reference SaaS profile events.- App assertions call the configured app assertion endpoint and can fail the run with a non-zero exit code.
- Scenario reports are JSON and Markdown capable from the CLI.
Fixture packs support repeatable local setup and assertions for:
- customers
- catalog products
- catalog prices
- customer payment-method fixture controls, including
payment_methods: []andpayment_methods_fixture: emptyfor no-card setup paths - customer default PaymentIntent outcome fixture controls through
default_payment_intent_outcomeorbilltap_default_payment_intent_outcomemetadata for direct and invoice-backed one-time payments - test clocks
- subscription graphs created through the normal checkout-completion path
- explicit subscription status/time fields for local lifecycle setup
- local refund and credit-note evidence
- optional stable checkout session, subscription, invoice, and payment intent IDs for provider-replacement tests that need exact fixture IDs
- fixture
refresolution throughGET /api/fixtures/resolve - seeded webhook events for fixture-created checkout, subscription, invoice, payment-intent, refund, and credit-note evidence
- missing
customer.subscription.createdevent backfill for pre-seeded or re-applied subscription fixtures - fixture-scoped snapshots
- assertion reports for customers, products, prices, checkout sessions, subscriptions, invoices, payment intents, refunds, credit notes, and timeline entries
Fixture metadata is written to created objects:
billtap_fixture_namebilltap_fixture_run_idbilltap_fixture_namespacebilltap_fixture_ref
Fixtures are intended for local and CI setup. They are not migration tooling and must not contain real card data, live credentials, or production customer data.
npm run smoke:sdk exercises the documented Stripe-like subset with the
official stripe-node SDK. The lane covers customer, product, price, checkout
session, event, webhook endpoint, and related retrieve/list flows against an
isolated local Billtap server by default. It can target an existing Billtap
server with BILLTAP_STRIPE_SDK_SMOKE_BASE_URL.
Billtap does not support or claim:
- Full Stripe API coverage.
- Real payment processing or live payment success paths.
- Stripe Dashboard behavior.
- Real card data, PAN, CVC, expiration fields, or live credentials.
- Full Stripe request idempotency-key semantics across all endpoints. Billtap
only caches same-process
POSTresponses for supported API simulation and rejects same-key parameter mismatches. - Direct charge creation, balance transactions, external-account verification, real Connect onboarding, real payouts/transfers settlement, mandates, tax, full discount redemption, full subscription-schedule billing, quotes, or usage-based metering.
- Full refund and credit-note ledger behavior, including balance transactions, settlement, failed refund processing, line tax allocation, PDF rendering, and provider accounting side effects.
- PaymentIntent customer-balance application, incremental authorization, bank microdeposit verification, and full payment-method option parity.
- Stripe-hosted Checkout or Billing Portal parity.
- Provider-specific settlement, risk, tax, invoice rendering, fraud, account, payout, or dispute behavior.
- Complete webhook event coverage.
- Full invoice rendering, send, void, line mutation, automatic collection, tax, and dunning automation. Billtap supports the checkout retry subset and the bounded manual one-time invoice flow documented above.
Use Stripe testmode or the real provider sandbox as the fallback lane for these behaviors.
Before adding a new compatibility claim:
- Add or update a fixture, scenario, or contract test.
- Document the endpoint, event, action, or boundary here.
- State unsupported provider behavior instead of silently approximating it.
- Run the release verification in
docs/RELEASE.md.