Your server-side application does not have to hold a Stripe secret key to perform every Stripe operation. It can authenticate to a controlled service, submit a native Stripe request, and let that service attach the provider credential before forwarding the request.
This is an architectural option, not a requirement. Direct Stripe access remains simpler for many applications, especially when the application needs broad Stripe API coverage and already runs in a tightly controlled server environment.
The normal direct-integration model
Most Stripe integrations follow this path:
- The application loads a Stripe secret key from its deployment environment or secret store.
- The server uses the Stripe SDK or sends an HTTPS request to Stripe.
- Stripe authorizes the operation according to that key and returns the response.
That is a valid design. A secret manager can improve storage, delivery, and rotation, but an authorized application still receives the key at runtime. If the application needs the credential itself, direct access is usually the least complicated choice.
The problem changes when many applications, workers, preview environments, or coding tools would otherwise need copies of the same provider credential. In that case, keeping the key in a better store still leaves the application able to use the full authority attached to it. A relay can place the credential outside the application process and grant the application only the operation boundary it needs.
The credential-boundary model
With a controlled relay, the flow becomes:
Application -- authenticated native Stripe request --> Gateman -- Stripe credential --> Stripe
The application sends the method, path, body, and supported Stripe headers to the relay using its own application identity. The relay authenticates the application, checks its capability, selects the configured environment credential, calls Stripe, and returns the provider response. The application can perform the operation without receiving sk_live_... or sk_test_....
The boundary is useful when the application needs authorized Stripe operations rather than possession of the Stripe credential. The application still has a sensitive Gateman identity and can still perform whatever its configured capability allows. Credential isolation is not the same as removing authorization responsibility.
Keep the request recognizably Stripe
Gateman's stripe:use capability accepts native Stripe request semantics through POST /stripe/request. A request includes an environment and a native method, path, body, and optional supported headers:
{
"environment": "production",
"method": "POST",
"path": "/v1/payment_intents",
"body": {
"amount": 2500,
"currency": "aud",
"metadata": {
"order_id": "order-4821"
}
},
"headers": {
"Idempotency-Key": "order-4821-payment-intent"
}
}
The outer body is JSON for Gateman. The nested Stripe body is not sent as JSON: a nested array is form-encoded using bracket notation, while a nested string is forwarded unchanged. For example, metadata[order_id]=order-4821 is sent in Stripe's normal form-style request. This distinction matters when translating code from a Stripe SDK or raw HTTP client.
The fixed provider origin is https://api.stripe.com. The application supplies a relative path beginning with /v1/; it cannot choose an arbitrary host or send a URL containing a different scheme. Supported methods are GET, POST, and DELETE. PUT and PATCH are rejected, as are general multipart or file-upload requests and public streaming-response contracts. The relay is deliberately a bounded provider-native transport, not an unrestricted HTTP proxy.
The supported nested headers are Idempotency-Key, Stripe-Account, Stripe-Version, and Content-Type. Gateman constructs the Stripe bearer authorization header from the configured application credential; the application cannot replace it through the nested header map. The provider still decides whether a particular path, parameter, account, or credential is authorized.
Read the Stripe capability reference and Contract v1 for the authenticated request boundary.
Methods, paths, and body shapes
GET is useful for retrieving customers, payment intents, accounts, and other resources. Its body should normally be empty; query parameters belong in the /v1/... path. POST covers creation and updates that Stripe exposes through POST, and DELETE covers native deletion endpoints. The fact that a Stripe endpoint exists does not make every HTTP verb available through Gateman.
The path validator prevents host selection, schemes, fragments, control characters, path traversal, and ambiguous double slashes. A path such as /v1/payment_intents/pi_123 is valid; https://api.stripe.com/v1/... is not the contract. Keeping the origin and path rules fixed makes the relay easier to audit and prevents it becoming a generic outbound request service.
For a body supplied as a string, Gateman forwards that string unchanged. For a body supplied as an array, Gateman uses RFC 3986 form encoding, including bracket notation for nested arrays and objects. It does not serialize the array as JSON, and null array values are omitted by the serializer. A non-empty body receives application/x-www-form-urlencoded unless the caller supplies a valid supported Content-Type.
The serialized request body is limited to 4 MiB. The limit applies to the outbound encoded body, not just the number of top-level fields in the outer JSON envelope. Large file transfers and general multipart payloads need a different architecture, such as a provider-supported direct upload flow.
Responses remain Stripe responses
For a response within the transport limit, Gateman preserves Stripe's native HTTP status and response body. A successful Stripe response therefore remains a Stripe JSON response, and a Stripe validation or authorization error remains a native Stripe error response. Gateman does not rewrite every result into one universal {ok: true} or {ok: false} envelope.
Only documented response headers are forwarded: Content-Type, Request-Id, Idempotency-Key, Stripe-Version, and Retry-After, when Stripe provides them. The application should use the Stripe status, body, and error fields according to the endpoint it called. Different official clients may decode those results differently, but the relay contract does not pretend that all Stripe operations share one business-level response shape.
The decoded Stripe response body is limited to 4 MiB (4,194,304 bytes), after HTTP content decompression. If the response exceeds that limit, Gateman stops materializing it and returns a Gateman-generated 502 with stripe_response_too_large. No partial Stripe body is returned. The application must treat this as an uncertain provider-execution outcome: the missing response does not prove that Stripe did not execute the request, so blindly repeating a mutation can be unsafe.
The same caution applies to a timeout or transport failure. A relay failure can occur after Stripe has accepted a request but before the application receives its response. Record the application operation and reconcile provider state where the result matters.
SDK or raw HTTP?
The Stripe SDK normally constructs the request and sends it directly to Stripe. Passing a Gateman credential to the SDK as if it were a Stripe secret key does not turn the SDK into a relay client. A Gateman identity is authenticated under a different contract and should not be confused with a Stripe bearer credential.
The straightforward approach is a small HTTP client or official Gateman client that constructs the native request and sends it to POST /stripe/request. An SDK transport adapter may preserve familiar object methods, but it must deliberately handle the relay URL, Contract v1 authentication, supported headers, response decoding, and error mapping. It must also avoid assuming that Gateman has the same retry or pagination behavior as a Stripe SDK.
Use direct Stripe access when broad SDK coverage is the priority. Use the relay boundary when keeping provider credentials outside the application removes enough distribution and maintenance work to justify the additional hop. The relay adds latency, another availability dependency, and a supported-operation boundary. Those are real costs, not implementation details to hide.
Version and account context remain explicit
If the application supplies an allowed Stripe-Version, Gateman forwards it. If it omits the header, Stripe applies the relevant account-default behavior for that request; Gateman does not infer a version from an installed Stripe SDK. The application should understand the version it is asking for and test response changes.
For Connect, Stripe-Account identifies the connected-account context. Gateman allowlists and forwards that header, but it does not independently validate Connect authorization beyond its relay controls. Stripe's credential and account permissions remain authoritative. The application must enforce its own tenant and business authorization before selecting a connected account; an account ID supplied by a caller is not proof that the caller may use it.
This is an important separation of responsibilities. Gateman can prevent unsupported headers and hosts, while Stripe decides whether the configured credential may act in the selected account context. The application decides whether its own authenticated user, order, tenant, or workflow may select that connected account in the first place.
Stripe's versioning reference and Connect authentication guide describe the provider-side behavior.
Idempotency and retry decisions
Record the application's payment or business intent and decide whether each request is safe to retry. For a PaymentIntent creation, use a stable Stripe Idempotency-Key for the same logical operation where appropriate. Stripe interprets that nested header and can return the result associated with an earlier request according to its own idempotency rules. The key is not a substitute for the application's order or reconciliation record.
Gateman's optional outer X-GM-Idempotency-Key is included in Contract v1 signing. For stripe.use, it does not deduplicate the relay call, cache a response, or prevent another Stripe request. It has a different meaning from the nested native Stripe Idempotency-Key.
The official PHP, Node.js, Python, and Go Gateman clients do not automatically retry Stripe requests. A repeated invocation can reach Stripe again and consume another Gateman operation, even when Stripe returns a previously retained idempotent result. After a timeout, transport failure, or oversized response, use a stable Stripe idempotency key where the operation supports it, then reconcile rather than assuming the provider did nothing.
Stripe's idempotent requests reference explains which provider requests can be retried safely and how Stripe treats repeated keys. A GET or DELETE request should not be made to depend on a Gateman response cache; it remains a native provider request.
Webhooks are a separate return path
An outbound Stripe request through Gateman does not mean webhook traffic comes back through Gateman. If a Stripe webhook endpoint is configured for your application, Stripe delivers the event directly to the callback URL. Your application must receive the raw request, verify Stripe-Signature with the endpoint secret, deduplicate events, and process its own business state.
Only an explicitly designed webhook relay changes that path. Such a relay must verify Stripe's signature on the original bytes, then establish a separate authenticated delivery to the application. The original Stripe signature does not authenticate a modified body or a new relay hop. Gateman's POST /stripe/webhook/setup configures an endpoint but does not receive later deliveries.
Operational trade-offs
The relay adds a network hop and makes Gateman availability part of the application's Stripe availability. Measure the latency of the full path, set timeouts that leave room for the application response, and distinguish a Gateman admission error from a native Stripe error. Keep the Stripe Request-Id when troubleshooting and avoid logging credentials or full payment payloads.
Provider-specific edge cases remain provider-specific. Pagination, expansions, Connect account context, API-version changes, idempotency, and endpoint-specific error semantics still belong to Stripe and the application. Gateman does not turn those concerns into one generic payment abstraction. For large responses, uploads, streaming, browser payment collection, or workflows requiring broad SDK behavior, direct integration may be the better fit.
Where Gateman fits
Gateman is useful when an application should call Stripe through a narrow, app-authorized provider boundary without storing the Stripe secret key. It keeps the request provider-native, selects the configured test or live credential from the declared environment, and leaves Stripe's response and application business logic visible.
It is not a replacement for Stripe, a universal API abstraction, or a webhook delivery service. Choose the boundary because it removes credential plumbing and repeated setup work, not because direct Stripe integration is inherently wrong.