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_KEY | connector_accounts.credential_ref → Secret Manager path like projects/gatelithix-core/secrets/merchant-{short_id}-stripe-live |
STRIPE_WEBHOOK_SECRET | Same secret, stored as JSON: {"api_key": "sk_live_...", "webhook_secret": "whsec_..."} |
NMI_SECURITY_KEY | Per-account Secret Manager path (mode="live") |
NMI_SECURITY_KEY_SANDBOX | Per-account Secret Manager path (mode="test") |
FLUIDPAY_API_KEY | Per-account Secret Manager path (mode="live") |
FLUIDPAY_API_KEY_SANDBOX | Per-account Secret Manager path (mode="test") |
TSYS_DEVICE_ID | Per-account Secret Manager JSON: {"device_id": "...", "transaction_key": "..."} |
TSYS_TRANSACTION_KEY | Same 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)get400 connector_account_requiredon 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
- Inventory. Run the v2.2.x operator script to list every
(merchant, connector, mode)currently being charged. See the rootMIGRATION-v2.3-CONNECTOR-MODEL.mdfor the SQL. - Create Secret Manager entries for each credential set (one per merchant-connector-mode triple). Move any shared platform keys into per-merchant paths.
- Create connector accounts. For each triple,
POST /admin/merchants/{id}/connector-accountswithis_default=trueso the existing caller integration keeps working. - Verify in staging. Deploy v2.3 to staging first. Run your charge/refund/void smoke tests against a test merchant.
- Monitor
connector_account_requirederrors post-deploy. Any spike means a merchant was missed during back-fill — resolve by creating the account, no gateway rollback required. - Remove old env vars from deployment config once traffic is confirmed on the new path.
Next steps
- Connector Accounts — the full model reference.
- Authentication — Selecting a connector account — request-level override mechanics.
- Error Codes — all 8 new connector-account error codes.
- Changelog — v2.3.0 release notes.