Payments PendingOwning Session State Across Stripe and Kalshi Auth Flows in Integration Tests

Owning Session State Across Stripe and Kalshi Auth Flows in Integration Tests

Test stateful APIs by explicitly owning credentials and session state across API boundaries.

· 8 min read

Integration testing against stateful APIs requires an environment that remembers earlier operations, enforces valid transitions, and exposes the resulting state for assertions. For Stripe and Kalshi, that means owning credentials and session identifiers from bootstrap through the final business outcome. Stateful simulators provide that coverage without hitting production, while periodic live API checks detect drift between the simulator and the real service.

Why Stripe and Kalshi auth flows break at the seams between steps, not inside them

The seams are the joins between API operations. A call can return the expected response while leaving the next step with an absent credential, a stale object, or an identifier from another session.

Stripe’s checkout-to-webhook-to-fulfillment sequence makes that dependency concrete. The application creates a checkout session, receives a checkout.session.completed event with a session ID and payment_status, and passes the result to a downstream handler. That handler must associate the event with the session the application created. A successful webhook response alone does not establish that association.

Kalshi puts a different dependency at the connection boundary. WebSocket authentication happens during the handshake, so attaching credentials after connection setup cannot establish the required authenticated session. When both APIs appear in a test, Stripe’s payment lifecycle and Kalshi’s handshake-time credential binding impose separate conditions on the same business sequence. Neither can be inferred from a later success response. Both must be established before the test can trust its business assertions.

What each API's auth model actually requires you to carry across a test session

Stripe test sessions use test-mode secret keys that have the form sk_test_*. Stripe’s testing documentation requires test API keys in every API call, whether the application serves an interactive payment form or runs test code. Mixing test and live keys can cause errors that look like permissions failures. The key must therefore stay the same throughout the session.

Connect adds a separate credential to that model. Its OAuth flow produces a connected account’s access token, which is distinct from the platform’s API key. Stripe’s Connect testing documentation gives a test client_id for getting test access tokens, which authenticate calls made on behalf of the connected account. Card history also persists across calls. When a scenario depends on card-level state, an independent end-to-end scenario needs a different test card.

Kalshi supports Ed25519 and RSA keys, with Ed25519 selected by default in the web app’s key-creation dialog. Ed25519 signs the message directly, while RSA uses PSS padding, SHA-256, and digest-length salt. The key type determines the signing implementation. WebSocket credentials belong in the handshake, and signed requests depend on the correct time, PEM format, and path. Clock drift, a missing /trade-api/v2 prefix, or a query string included in the signed path can cause a 401.

Why a stateless mock cannot tell you whether the state you carried is coherent

A stateless mock can test handling of a known response. It cannot establish that the response follows from earlier operations when each endpoint simply returns a hardcoded result. A customer created in one step and referenced by a subscription later may be consistent only because the fixtures were written that way. The environment has not tested the relationship.

For Stripe, that gap spans authentication and payment state. A mock that accepts any credential-shaped string may return success for a mismatched Connect access token, then deliver a canned webhook whose session ID differs from the checkout session the test created. It may also return succeeded without enforcing the PaymentIntent’s required lifecycle transitions.

Kalshi exposes the same limitation at session setup. A mock that accepts trading operations without checking handshake authentication cannot distinguish a correctly authenticated connection from one that received credentials too late. Its success response conceals the boundary the test needs to exercise. The practical discriminator is “create then read, mutate then read.” If the second read cannot reflect the earlier mutation, the environment cannot establish that the session’s operations produced a coherent result. Auth-state testing follows the same pattern: credentials are bootstrapped and used, then checked through the effects they have.

Bootstrapping each auth flow in a way that makes session state explicit and inspectable

Treat credentials as named fixtures, validate their presence and format before business logic runs, and pass them deliberately to later steps. A failure should identify the credential involved. An undifferentiated auth-layer failure leaves the test author reconstructing which setup decision reached the failing request.

For Stripe, establish one named session variable containing the sk_test_* key and have every API call read from it. Avoid inline key strings and environment-variable lookups midway through the test. For Connect, run the OAuth exchange once at session start and store the resulting access token separately from the platform key. The Connect testing documentation gives ca_FkyHCg7X8mlvCUdMDao4mMxagUfhIwXb as the test client_id. Pass the platform key or connected-account token explicitly to the leg that requires it.

Cards are also chosen during setup. Document the transition the selected card must exercise, using pm_card_visa rather than raw card numbers in server-side code, or pm_card_authenticationRequired for a 3DS flow requiring a next action. For Kalshi, choose Ed25519 or RSA at setup and use the matching signing implementation for all later calls. Record the signing time as part of bootstrap. For a WebSocket test, make the authenticated handshake the first connection operation, assert its success, and stop before business logic if it fails.

Carrying credentials through lifecycle transitions without losing coherence

Keep forwarding the appropriate credential explicitly, but do not treat the resource object captured at setup as permanently authoritative. A PaymentIntent transition can change what the next request requires.

For a Stripe checkout flow, first create the checkout session with the named test credential and retain its returned ID. As payment operations change the PaymentIntent, use each operation’s response or re-fetch the object before proceeding. A flow that moves through requires_payment_method, requires_action, and succeeded must respond to the current stage. Stripe’s automated testing documentation describes how to create PaymentIntent objects at different lifecycle stages to test these outcomes, including the on-session authentication return case.

Next, match the webhook’s session ID against the saved creation result before checking fulfillment. Test signature verification with the webhook secret in scope when the endpoint was registered. If that secret changes but verification still uses the old one, the join breaks. For Kalshi, repeat handshake authentication whenever a dropped WebSocket connection is re-established, because the previous connection’s auth context does not persist across reconnects. Also detect 401s that follow a clock-drift pattern rather than assuming a signature valid at bootstrap remains valid throughout the session.

How a stateful simulator enforces the state machine so tests catch coherence failures automatically

Explicit forwarding makes state visible to the test author. A stateful simulator adds an independent constraint by refusing operations that violate its modeled lifecycle. Without those checks, every test author must get the sequence right and foresee every way its steps could fall out of sync. The simulator applies those rules across tests.

For Stripe, enforcement means treating PaymentIntent changes as transitions. A test that attempts to move directly from requires_payment_method to succeeded without the required intermediate steps must fail in the environment. Subscription states likewise need modeled transition rules. For Connect, the simulator should accept test tokens with the right shapes and enforce the verification state machine.

Kalshi enforcement starts at the authenticated handshake. A simulator must reject trading operations on an unauthenticated session and validate signing paths, including the /trade-api/v2 prefix and exclusion of query strings from the signed portion. The simulator also needs to check signing time to catch clock drift as well as mistakes in the path format. These checks belong below the individual business assertions.

In CI, each session needs its own state for those rules to work. Each run should create fresh state, bootstrap its own credentials, execute the lifecycle, and tear down the session. Shared auth state lets one run contaminate another. That causes flakiness that looks like a coherence failure in the application even though the fault is in the test environment.

Asserting on the cumulative world the test leaves behind, not just on individual call responses

Once the simulator enforces the sequence, assertions must examine its accumulated effects. Immediate responses remain useful, but they do not establish that the full integration achieved its business outcome. A webhook can receive a 200 without producing fulfillment. A trading message can be acknowledged without proving that the expected account or market state exists.

For Stripe, assert that the checkout session’s payment_status is paid and that the downstream handler recorded fulfillment for that session. Stripe’s agent benchmark used graders that checked the API objects created, including a successful test-mode Checkout Session after payment was completed in the UI. That provides a concrete model for human-authored tests. Send the same webhook event twice and assert that fulfillment was recorded exactly once. For Connect, use the issued access token and assert the expected capability responses.

For Kalshi, query the account or market state that the trading operations should have produced. Also assert that an incompatible key-type and signing combination fails during the handshake. This tests the authentication boundary directly. It also distinguishes an enforced session model from a simulator that accepts setup too leniently.

Finally, inject a 429 or 503 midway through the session and assert recovery without losing the bootstrapped auth context. A retry that silently re-authenticates has not demonstrated preservation of the original session. Apply “create then read, mutate then read” across the complete flow, querying the objects it should have created or changed.

Where to draw the line between simulator coverage and periodic live API validation

Stateful simulators belong in the development and CI workflow. Periodic live API validation catches changes in the real service that the simulator does not yet reflect. Stripe’s automated testing documentation places mock-based tests in continuous integration and recommends infrequent live calls to validate API responses while avoiding rate limits.

In practice, run stateful simulator tests on every commit and every CI build, with live validation scheduled daily or weekly to detect drift. For Kalshi WebSockets, that validation should exercise the full handshake with real credentials. Clock drift, PEM format, and path-prefix problems matter most when the simulator does not enforce real-time signing constraints. A passing simulated handshake cannot establish those properties on its own.

Running every integration test against the live API exposes CI to rate limits, network conditions, and third-party availability while still requiring inspection of session state.

Sources

  1. Can AI agents build real Stripe integrations? We built a benchmark to find out
  2. Testing Stripe Connect
  3. Automated testing
  4. Test card numbers
  5. Quick Start: WebSockets - API Documentation

More in Payment Flow Testing