Skip to content

Stripe Connect

Setup guide and operations for the stripe_connect payments provider. The shared API (initiate_payment, webhooks, hooks, saved methods) is documented in Payments.

For marketplace platforms – connect your users as Stripe Express accounts, collect platform fees, and transfer funds.

Uses the same Stripe account as the regular Stripe provider – no separate Stripe account needed – but it is its own instance with its own env vars:

"stripe_connect": {
"type": "stripe_connect",
"refresh_url": "/connect/onboard/refresh",
"return_url": "/connect/onboard/return"
}
Terminal window
KIRAK_PAYMENT_STRIPE_CONNECT_SECRET_KEY=sk_test_...
KIRAK_PAYMENT_STRIPE_CONNECT_WEBHOOK_SECRET=whsec_...
KIRAK_PAYMENT_STRIPE_CONNECT_PLATFORM_WEBHOOK_SECRET=whsec_...

The Connect methods (onboard_merchant, connect_checkout, …) use the first stripe_connect-type instance, or the one named by "provider" in the params.

Stripe treats a connected-accounts event and a platform-account event as two different webhook subscriptions, each with its own signing secret, even when both point at the same URL. Set up two endpoints in the Stripe Dashboard (Developers -> Webhooks), both at https://your-domain.com/payments/webhook/stripe_connect:

  • A connected-accounts endpoint, with Listen to events on Connected accounts checked, for the merchants’ own accounts: account.updated, account.application.deauthorized. Copy its signing secret into KIRAK_PAYMENT_STRIPE_CONNECT_WEBHOOK_SECRET.
  • A platform-account endpoint (the box unchecked). connect_checkout sessions are destination charges created on the platform account, and saved methods and off-session charges live there too (see “Off-session Charges” below), so all of their events arrive here: checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, charge.refunded, payment_intent.succeeded, payment_intent.payment_failed, payment_method.detached, payment_method.updated, payment_method.automatically_updated, customer.updated. Copy its signing secret into KIRAK_PAYMENT_STRIPE_CONNECT_PLATFORM_WEBHOOK_SECRET.

handle_webhook verifies an incoming request against KIRAK_PAYMENT_STRIPE_CONNECT_WEBHOOK_SECRET first, then against KIRAK_PAYMENT_STRIPE_CONNECT_PLATFORM_WEBHOOK_SECRET (if configured) when the first check fails, so both endpoints can deliver to the same route. platform_webhook_secret is optional only for an app that uses merchant onboarding alone; connect_checkout, saved payment methods and off-session charges all report through the platform-account endpoint.

When a stripe and a stripe_connect instance share one Stripe account, both receive the platform’s setup sessions and PaymentIntents. Kirak stamps metadata.kirak_instance (the instance name) on the setup sessions, saving checkouts and off-session PaymentIntents it creates, and an instance skips any tagged for another instance, so a method is saved once and an off-session charge is settled by the instance that made it.

Known limitation: Connect checkout sessions hardcode payment_method_types: ["card"] (the regular Stripe provider does not), so Apple Pay/Google Pay/ACH/SEPA are not offered even where Stripe would otherwise auto-enable them. Not yet configurable.

result = await kirak.payments.onboard_merchant({
"user_id": "42",
"email": "merchant@example.com",
})
# result["data"]["onboarding_url"] -> redirect merchant here to complete Stripe onboarding
result = await kirak.payments.create_onboarding_link({
"user_id": "42",
})
result = await kirak.payments.get_merchant_status({
"user_id": "42",
})
# result["data"]["charges_enabled"] -> bool
# result["data"]["payouts_enabled"] -> bool

Charge a customer through a connected merchant account:

result = await kirak.payments.connect_checkout({
"user_id": "42", # customer user ID
"merchant_user_id": "99", # connected merchant user ID
"amount": 1999,
"currency": "USD",
"name": "Service",
})

Platform fee is read from merchant_accounts.platform_fee_percent (your model).

Charge a saved payment method (see Saved Payment Methods and Off-Session Charges) as a destination charge, with the platform fee and transfer going to the same connected merchant as connect_checkout:

result = await kirak.payments.charge_off_session({
"user_id": "42", # customer user ID
"merchant_user_id": "99", # connected merchant user ID
"payment_method_id": 7,
"amount": 10000,
"currency": "USD",
"name": "Order",
"idempotency_key": "order-42-retry-1",
})

The saved method and customer live on the platform account, same as the regular Stripe provider – only the charge itself is routed to the merchant. merchant_user_id or stripe_account_id is required; an unresolved or not yet activated merchant raises before any transaction row is written.

await kirak.payments.update_merchant_fee({
"user_id": "99",
"is_paid_user": True, # True -> 5% fee, False -> 10% fee (configurable)
})

Both Stripe Dashboard endpoints (see “Setup” above) deliver to the built-in route POST /payments/webhook/stripe_connect. after_webhook receives these provider-neutral event names (see Webhook Hooks):

data["event"] Gateway events
payment_completed checkout.session.completed (only when paid), checkout.session.async_payment_succeeded, payment_intent.succeeded (off-session charges only)
payment_failed checkout.session.async_payment_failed, payment_intent.payment_failed (off-session, declined)
refund_completed charge.refunded
merchant_account_updated account.updated
merchant_deauthorized account.application.deauthorized
payment_method_saved checkout.session.completed of a setup session
payment_method_removed payment_method.detached
payment_action_required payment_intent.payment_failed with authentication_required (off-session)

A redelivered event is skipped by its event id, recorded per instance (see Repeated deliveries).