Skip to content
ExponaBLOG
PAYMENT ENGINEERING & STRIPE INTEGRATION · DAY 004 / 100

Stripe account setup for developers in Payments & Stripe

Start with the right foundations.

Expona Admin7 min read

The importance of getting Stripe account setup right

We've all felt the panic of accidentally running a live transaction in development, or worse, discovering that a junior team member was using production API keys in staging. Stripe account setup for developers in Payments & Stripe is one of those foundational tasks that feels simple but has outsized impact on your entire engineering workflow. Getting it wrong early means technical debt, security vulnerabilities, and operational headaches that compound over months.

In this guide, we'll walk through the concrete patterns that teams use to keep their Stripe integrations clean, secure, and scalable from day one.

Use test mode keys locally

Stripe provides two separate sets of API keys: test and live. Test keys are your sandbox—they'll never charge anyone, they never appear in reports to payment processors, and they're completely free to experiment with. Yet we see teams mixing environments constantly, either out of habit or misconfiguration.

The rule is simple: develop and test exclusively with test mode keys on your local machine and in lower environments. Stripe's test mode is feature-complete. You can simulate charges, refunds, disputes, webhooks, and subscription lifecycle events. Nothing you do in test mode will affect your production payment volume, your processing fees, or your merchant account standing.

Here's how to identify which keys you're holding:

  • Test mode secret keys start with sk_test_
  • Test mode publishable keys start with pk_test_
  • Live mode secret keys start with sk_live_
  • Live mode publishable keys start with pk_live_

In your local environment file, enforce test keys as a first line of defense:

typescript
// lib/stripe-client.ts
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  apiVersion: '2023-10-16',
});

// Fail fast if someone accidentally loads a live key locally
if (process.env.NODE_ENV === 'development' && 
    process.env.STRIPE_SECRET_KEY?.startsWith('sk_live_')) {
  throw new Error('❌ Live Stripe keys detected in development. Use sk_test_ keys only.');
}

export default stripe;

This simple check has saved us from subtle bugs more than once. When a developer copies environment variables between machines, this guard catches the mistake immediately.

Restricted API keys per service

Most teams start with a single pair of API keys: one secret, one publishable. As the codebase grows, you'll have a payment processing service, a webhook listener, a billing reconciliation job, a customer portal, and a React frontend—all needing Stripe credentials. Using the same key everywhere means one compromised service exposes your entire payment infrastructure.

Stripe's restricted API keys let you grant scopes per service. A webhook listener doesn't need to create customers or charges; it only needs to read events. Your billing frontend doesn't need to refund charges. Your reconciliation job doesn't need to process payments.

Here's how we typically organize restricted keys in a mid-size payment system:

Service Required Scopes Key Name
Payment Processing API Charges (create, read), Customers (create, read, update), Payment Intents (create, read) sk_test_payments_api
Webhook Handler Events (read), Charges (read), Subscriptions (read) sk_test_webhooks
Billing Dashboard Charges (read), Customers (read), Invoices (read) sk_test_billing_read
Reconciliation Job Charges (read), Refunds (read), Payouts (read) sk_test_reconciliation
Frontend (Publishable) Payment methods (create), Tokens (create) pk_test_frontend

Create these keys in the Stripe Dashboard under Account Settings → API Keys → Restricted Keys. Each key gets a human-readable name and explicit permissions. If a credential leaks, you can revoke just that key without disrupting the entire platform.

In practice, your service initialization looks like this:

typescript
// services/payment-processing.ts
import Stripe from 'stripe';

export const paymentStripe = new Stripe(
  process.env.STRIPE_SECRET_PAYMENTS_API,
  { apiVersion: '2023-10-16' }
);

// services/webhook-handler.ts
export const webhookStripe = new Stripe(
  process.env.STRIPE_SECRET_WEBHOOKS,
  { apiVersion: '2023-10-16' }
);

// services/billing-dashboard.ts
export const dashboardStripe = new Stripe(
  process.env.STRIPE_SECRET_BILLING_READ,
  { apiVersion: '2023-10-16' }
);

Each service only has access to what it needs. When you rotate keys annually or respond to an incident, you can update one service at a time without a coordinated deploy.

Separate accounts or sandboxes per environment

For teams running staging and production in parallel, we recommend separate Stripe accounts entirely. This keeps test data completely isolated from live transactions and gives you independent rate limits, webhook endpoints, and account settings per environment.

Setting up two Stripe accounts takes 15 minutes and eliminates an entire class of environment-bleeding bugs:

  • Production Stripe Account: Lives behind rigorous access controls. Only senior engineers have live mode access. Webhooks are hardened and PCI-audited. Reconciliation is automated and monitored.
  • Staging Stripe Account: Uses test mode only. Engineers freely experiment. Webhook endpoints can fail without impact. Data retention can be aggressive (for speed, not compliance).

If your organization uses single sign-on (SSO), you can manage dashboard access through identity providers, ensuring people only access the right account in the right context.

Your environment-specific configuration then looks like:

typescript
// config/stripe.ts
const stripeConfig = {
  development: {
    secretKey: process.env.STRIPE_SECRET_DEV,
    publishableKey: process.env.STRIPE_PUBLISHABLE_DEV,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET_DEV,
  },
  staging: {
    secretKey: process.env.STRIPE_SECRET_STAGING,
    publishableKey: process.env.STRIPE_PUBLISHABLE_STAGING,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET_STAGING,
  },
  production: {
    secretKey: process.env.STRIPE_SECRET_PROD,
    publishableKey: process.env.STRIPE_PUBLISHABLE_PROD,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET_PROD,
  },
};

export const getStripeConfig = (env: string) => {
  const config = stripeConfig[env as keyof typeof stripeConfig];
  if (!config) {
    throw new Error(`Unknown environment: ${env}`);
  }
  return config;
};

With separate accounts, your staging team can safely test subscription cancellations, refund workflows, and 3D Secure challenges without worrying about touching production data.

Store keys in a secrets manager

Never commit API keys to version control, never pass them as command-line arguments, and never email them. Stripe keys are as sensitive as database passwords. Treat them accordingly.

Use your cloud provider's secrets management service:

  • AWS: AWS Secrets Manager or Systems Manager Parameter Store
  • GCP: Google Secret Manager
  • Azure: Azure Key Vault
  • Self-hosted: HashiCorp Vault or Sealed Secrets

Your deployment pipeline retrieves secrets at runtime, never storing them on disk. In development, use a .env.local file (gitignored) for convenience, but enforce the same pattern in CI/CD.

A typical deployment flow:

  1. Your CI/CD pipeline triggers on code push.
  2. Before container startup, retrieve secrets from the secrets manager.
  3. Inject secrets as environment variables.
  4. Container process reads from environment only.
  5. After shutdown, secrets are garbage-collected.

Here's a Kubernetes example using secrets:

typescript
// kubernetes/deployment.yaml (conceptual)
apiVersion: v1
kind: Secret
metadata:
  name: stripe-keys
type: Opaque
stringData:
  STRIPE_SECRET_KEY: sk_test_... # Retrieved from secrets manager
  STRIPE_PUBLISHABLE_KEY: pk_test_...
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  template:
    spec:
      containers:
      - name: payment-service
        image: payment-service:latest
        envFrom:
        - secretRef:
            name: stripe-keys

Rotate keys periodically (at least annually). Stripe makes this painless: create a new key, update your deployments, verify functionality, then revoke the old key. No downtime required.

Common mistakes

Mixing test and live keys: A .env file that works locally uses test keys, but someone copies it to production and forgets to swap. Always validate the key prefix at startup and fail loud.

Using the same key for all services: When that key leaks, you've compromised your entire payment system. Restricted keys add almost no overhead and dramatically reduce blast radius.

Storing keys in code comments: "Here's the old key if you need to revert" comments get committed and indexed by search engines. Rotate instead of keeping copies.

Sharing webhook endpoints across environments: Your staging webhook handler shouldn't write to your production database. Use environment-specific webhook URLs and endpoints.

Ignoring Stripe API versioning: Stripe evolves its API. Pin a specific version (like 2023-10-16) when initializing the client. This keeps behavior consistent and lets you upgrade deliberately.

Key takeaways

  • Always use test mode keys in development and lower environments. Enforce this at application startup with guards.
  • Create restricted API keys per service to limit the blast radius if credentials are compromised.
  • Separate Stripe accounts for staging and production eliminates environment-bleeding bugs and keeps test data isolated.
  • Never commit keys to version control. Use a secrets manager (AWS Secrets Manager, Vault, etc.) and inject at runtime.
  • Rotate keys at least annually and immediately if compromised. Stripe's API makes this seamless.
  • Validate configuration on startup with explicit checks—fail fast if keys don't match the expected environment.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles