Skip to Content
Migration Guide — v2.3

Migration Guide — v2.3 Connector Model

Breaking change. v2.3 removes the platform-default connector credential environment variables entirely. Charges without a resolvable connector_account now return 400 connector_account_required. There is no deprecation period — cutover is the v2.3 deploy date.

v2.3 introduces the connector account model: every charge the gateway settles is bound to a specific per-merchant, per-mode (connector, key_kind) credential row. The v2.2.x behaviour of falling back to platform-wide env vars (STRIPE_API_KEY, NMI_SECURITY_KEY, FLUIDPAY_API_KEY, TSYS_DEVICE_ID, TSYS_TRANSACTION_KEY, …) is gone.

This guide walks you through the hard cut.


Who is affected

  • Every merchant currently charging through the gateway whose credentials were sourced from platform env-var defaults (all of them, as of the v2.3 cutover).
  • Every API client posting to /v1/payments/authorize, /v1/payments/capture, /v1/payments/sale, /v1/payments/refund, or /v1/payments/void.
  • Every ISO admin who provisions merchants: merchant creation now requires at least one connector account before the merchant can charge.

If you are on v2.2.x or earlier, you are affected.


What breaks

Before (v2.2.x)After (v2.3)
Platform-wide STRIPE_API_KEY env var resolves every Stripe charge.Removed. Merchant must have a connector_account row for Stripe.
One credential per merchant per connector.N credentials — one per connector account.
Payment request implicitly routes to “the” credential.Payment request routes to the merchant’s is_default=true account OR the caller-specified connector_account.
No connector_account_short_id on payment response.connector_account_id + connector_account_short_id now in every PaymentIntentResponse.

The concrete failure mode: a POST /v1/payments/authorize (or any write) from a merchant with zero active connector_accounts for the routed processor returns:

{ "error": { "type": "invalid_request_error", "code": "connector_account_required", "message": "This merchant has no default connector account for the routed processor. Pass `connector_account` in the body or the X-Connector-Account header.", "param": "connector_account", "request_id": "req_01HK9ABCD3F7X1YZ2Q3W4E5R6T" } }

The response includes an available_accounts list when the caller is authenticated for the correct merchant and the merchant has ≥1 account but none marked default. Unauthenticated / wrong-merchant callers never see this list (anti-enumeration).


Before / after

Single-account merchant — request body is unchanged

If the merchant has exactly one active connector account for the routed (connector, mode, key_kind), and it’s marked is_default=true, the v2.2.x request body works verbatim:

Before (v2.2.x) and After (v2.3) — identical:

POST /v1/payments/authorize Authorization: Bearer sk_live_xxx Idempotency-Key: idem_01HK... Content-Type: application/json { "amount": 5000, "currency": "usd", "gateway_token": "tok_live_abc" }

The only visible change: the response now includes connector_account_id and connector_account_short_id so you know which account settled the charge.

Multi-account merchant — caller MUST specify

If the merchant has ≥2 active accounts for the routed triple, the caller must pick one explicitly. Body field wins over header.

After (v2.3) — body field:

POST /v1/payments/authorize Authorization: Bearer sk_live_xxx Idempotency-Key: idem_01HK... Content-Type: application/json { "amount": 5000, "currency": "usd", "gateway_token": "tok_live_abc", "connector_account": "ca_abc123def4" }

After (v2.3) — header (equivalent):

POST /v1/payments/authorize Authorization: Bearer sk_live_xxx Idempotency-Key: idem_01HK... X-Connector-Account: ca_abc123def4 Content-Type: application/json { "amount": 5000, "currency": "usd", "gateway_token": "tok_live_abc" }

If both are set, the body wins when they agree and the gateway returns 400 connector_account_ambiguous when they disagree.


Env-var → per-account mapping

Operators upgrading infrastructure should map each removed env var to a per-account Secret Manager path. The connector-account row then references that path via credential_ref.

Old env var (v2.2.x)v2.3 destination
STRIPE_API_KEYconnector_accounts.credential_ref → Secret Manager path like projects/gatelithix-core/secrets/merchant-{short_id}-stripe-live
STRIPE_WEBHOOK_SECRETSame secret, stored as JSON: {"api_key": "sk_live_...", "webhook_secret": "whsec_..."}
NMI_SECURITY_KEYPer-account Secret Manager path (mode="live")
NMI_SECURITY_KEY_SANDBOXPer-account Secret Manager path (mode="test")
FLUIDPAY_API_KEYPer-account Secret Manager path (mode="live")
FLUIDPAY_API_KEY_SANDBOXPer-account Secret Manager path (mode="test")
TSYS_DEVICE_IDPer-account Secret Manager JSON: {"device_id": "...", "transaction_key": "..."}
TSYS_TRANSACTION_KEYSame Secret Manager entry as above (single JSON blob per TSYS account)

After migration, none of these environment variables are read by the gateway. You can (and should) remove them from your deployment config.

Do not store credentials in your own application env vars and pass them on each request. The v2.3 model deliberately places credentials in Secret Manager under Gatelithix IAM control so that rotation, audit, and PCI scope containment work uniformly. The credential_ref on a connector_account is the only supported path.


No deprecation period — hard cut

There is no gradual rollover. On the v2.3 deploy date:

  • The old env-var fallback code paths are removed from the gateway, connectors, and vault.
  • Merchants without at least one active connector account per routed (connector, mode, key_kind) get 400 connector_account_required on every charge.
  • There is no feature flag, no soft-fail, no “just this once” bypass.

Back-fill connector_accounts rows for every active merchant before the v2.3 deploy — ideally in a staging rehearsal first.

The reasoning: platform-default credentials are a PCI scope hazard (one rotation incident touches every merchant) and a security hazard (one leaked env var covers the entire fleet). A graduated deprecation would leave both hazards live for the duration of the rollover. The hard cut forces every merchant onto per-account credentials on day one.


Migration checklist

  1. Inventory. Run the v2.2.x operator script to list every (merchant, connector, mode) currently being charged. See the root MIGRATION-v2.3-CONNECTOR-MODEL.md for the SQL.
  2. Create Secret Manager entries for each credential set (one per merchant-connector-mode triple). Move any shared platform keys into per-merchant paths.
  3. Create connector accounts. For each triple, POST /admin/merchants/{id}/connector-accounts with is_default=true so the existing caller integration keeps working.
  4. Verify in staging. Deploy v2.3 to staging first. Run your charge/refund/void smoke tests against a test merchant.
  5. Monitor connector_account_required errors post-deploy. Any spike means a merchant was missed during back-fill — resolve by creating the account, no gateway rollback required.
  6. Remove old env vars from deployment config once traffic is confirmed on the new path.

Next steps