Stripe, Paddle, and Dodo Payments all follow the same setup shape now: an org admin adopts the connector and optionally customizes where a confirmed payment lands, then each workspace supplies its own provider credentials and its own product catalog. There's no separate flow to author — a checkout is a direct action call, same as any other connector action.

This replaces the older "checkout flow" model

Earlier docs and workshop material described a named checkout-flow config (flow_id, Customize → Flows, tenant_input_keys). That model no longer exists — this article describes the current one. If you're following older material that mentions flow_id, treat it as out of date.

1 · Org admin adopts the connector

Org Settings → Integrations → find the provider → Enable. This is the same enable step as any other connector — it makes the connector selectable for this org and stages it (disabled) for every workspace. Existing workspaces pick it up automatically; a Sync from org button on the workspace's own Connectors screen is the manual fallback if one doesn't.

2 · Org admin customizes the webhook landing (optional)

Open the connector's Customize → Webhook tab. This is the *only* thing left org-customizable per provider — everything else about how the connector talks to the provider (auth type, request shape, signature scheme) ships with the platform and isn't editable per org. For each event type the provider can send, choose:

  • Target Entity — which of your entities a confirmed event writes to.
  • Operationinsert a new row, or update an existing one.
  • Target Row ID — a template for which row, usually {{ipaas_ref.entityRef}} (the optional entity_ref your app passed when it started the checkout).
  • Field Mapping — which entity fields get which values from the event payload.

Promote to production before it takes effect there

This save lands in your dev environment first. Promote it through the same Deployments review/publish flow you already use for entities, events, and Logic Blocks — a production workspace's webhooks won't land anywhere until you do.

3 · What each field means

  • Secret Key / API Key — your provider account's server-side API credential. Entered once per workspace, encrypted, never returned by any API. This is what nostackai uses to call the provider on your behalf (create the checkout, cancel a subscription).
  • Webhook Signing Secret — a *second*, separate secret the provider generates when you register nostackai's webhook URL in its dashboard. Used only to verify an incoming webhook actually came from the provider — never sent anywhere, never used for outbound calls.
  • Product Catalog — a free-form label → provider-id map you define yourself (see step 5). Your app sends the label; nostackai resolves it to the real price/product/plan id before calling the provider.
  • Client-side token (Paddle only) — a public, safe-to-embed token for launching Paddle's inline checkout overlay from your own frontend. Never stored by nostackai — see the Paddle-specific section below.

4 · Workspace enters credentials

Tenant Settings → Connectors → select the provider → Credentials section → enter the Secret/API Key and Webhook Signing Secret from your own provider account (test-mode/sandbox to start) → Save. This section has its own independent save action — saving credentials can never touch or overwrite anything else on this screen.

Paddle: the API key needs write access to Transactions AND Subscriptions

A Paddle key scoped to only one of the two fails with "not authorized to create|read transaction" (or the equivalent for subscriptions) the first time you call checkout or cancel. Grant both scopes when creating the key (Paddle Dashboard → Developer Tools → Authentication → API keys).

5 · Workspace sets up its Product Catalog

The checkout action's product field is a label you choose, not the provider's raw price/product/plan id — this is what keeps your app code stable even if you change prices later. On the same Connectors screen, under Product Catalog, add rows mapping a label to the real id from your provider account:

json
{ "starter_monthly": "price_1AbC...", "starter_annual": "price_1XyZ..." }

There's no organization-level list of "valid" labels to keep in sync — whatever labels you enter here are the ones your own app can use. A checkout call with a label that isn't in your catalog fails with a clear error rather than reaching the provider with garbage.

6 · Call checkout from a client app

http
POST /app/{orgcode}/ipaas/{connector_id}
{ "action_id": "checkout_price_recurring", "fields": { "product_name": "starter_monthly", "success_url": "...", "cancel_url": "..." } }

→ { "connector_id": "stripe", "action_id": "checkout_price_recurring", "result": { "checkout_url": "https://..." } }

The action is a body field (action_id), not part of the URL. product_name is the label from step 5, resolved server-side — the raw id never appears in your client code. Redirect the user to result.checkout_url. Confirmation arrives asynchronously via the webhook, landing on whatever entity you configured in step 2 — read that entity the same way you'd read any other data your app manages.

You never send who's calling

There's no user_id field to fill in — nostackai stamps it automatically from your own authenticated request, so a checkout can't be attributed to the wrong user. If your fields list shows a field like this described as "set automatically," leave it out of your request entirely; sending it does nothing.

Provider differences at a glance

  • Stripe — two checkout actions, both catalog-label-based (no ad-hoc pricing — the amount/currency always comes from the Price your label points at, never from the caller): checkout_price (one-time) and checkout_price_recurring (subscription). Credential fields: Secret Key, Webhook Signing Secret.
  • Paddle — one checkout action handles both one-time and subscription; which mode runs depends on which price your label points at. Credential fields: API Key, Webhook Signing Secret. Also supports an inline overlay — see below.
  • Dodo Payments — two actions, both via Checkout Sessions: checkout (subscription, catalog label) and checkout_onetime (one-time "Single Payment" product, catalog label — same Product Catalog mechanism, just point a second label at a one-time product). Neither needs a billing address upfront — Dodo collects it on the hosted payment page. Credential fields: Secret Key, Webhook Signing Secret.

Paddle only: the inline overlay option

Every provider's checkout response includes a redirect URL — send the user there and it always works. Paddle's checkout response *additionally* includes a raw transaction id, which your own frontend can use to open Paddle's checkout as an inline overlay instead of a page redirect, using Paddle's own Paddle.js SDK and your Paddle account's Client-side token (Paddle Dashboard → Developer Tools → Authentication → Client-side tokens — a public token, safe to embed in frontend code).

json
// Paddle checkout response — both are always present, use either or both
{ "result": { "checkout_url": "https://...", "transaction_id": "txn_..." } }
js
import { initializePaddle } from '@paddle/paddle-js';

const paddle = await initializePaddle({ token: yourClientSideToken, environment: 'sandbox' });
paddle.Checkout.open({ transactionId: result.transaction_id });

Nothing to configure on our side

The client-side token is entirely your own app's concern — it's never sent to or stored by nostackai. Whether you use the redirect, the overlay, or offer both to your own end users is entirely up to your frontend; nostackai's response doesn't change either way.

Gating checkout behind conditional logic — not yet supported

Known limitation

Today, product-label resolution (step 5) only works through the direct checkout route shown in step 6. Calling a payment connector's checkout action from inside a Logic Block, or from an automation event destination, does not currently resolve a Product Catalog label — it would need the real provider id passed in directly. If you need to gate a checkout behind eligibility logic (a promo code, a plan-downgrade rule), that path isn't supported yet; it's on the roadmap.