Automate Stripe customer management using POST /v1/customers to create, PATCH /v1/customers/{id} to update metadata, and GET /v1/customers/search to query by email or custom fields. Attach payment methods via POST /v1/payment_methods/{id}/attach. Key gotcha: test mode customers (cus_test_*) don't exist in live mode — 'No such customer' almost always means a test/live key mismatch. Rate limit: 100 req/sec live mode; search endpoint is more aggressively rate-limited.
API Quick Reference
API Key (Bearer token)
100 requests/second (live mode)
JSON
Available
Understanding the Stripe API
Stripe Customer objects are the central entity in your billing architecture — they hold contact information, payment methods, subscriptions, invoices, and metadata. Keeping your Stripe customer records synchronized with your application's user database enables accurate billing, customer lookup, and lifecycle automation.
The Customer API is straightforward REST: create customers on signup, update metadata when user profiles change, attach payment methods when cards are added, and use the search endpoint to find customers by email or custom metadata fields. Every subscription, invoice, and charge is linked to a Customer, so well-managed customer records are the foundation of accurate reporting.
The most critical gotcha is test/live mode isolation. Stripe completely separates test and live mode data — a customer created with sk_test_* exists only in test mode. Calling the live mode API for a test customer ID returns 'No such customer'. Always verify you're using the right key for the right environment. Official documentation: https://stripe.com/docs/api/customers
https://api.stripe.comSetting Up Stripe API Authentication
Stripe uses simple API key authentication. For customer management, you only need the secret key. The test and live environments are completely isolated — make sure to use the right key for each environment to avoid the 'No such customer' error from mode mismatches.
- 1Log in to your Stripe Dashboard at dashboard.stripe.com
- 2Click 'Developers' in the top navigation, then 'API keys'
- 3Copy your Secret key — sk_live_* for production, sk_test_* for development
- 4Set STRIPE_SECRET_KEY environment variable in your application
- 5Confirm the key works with a test call to GET /v1/customers?limit=1
- 6For restricted access, create a Restricted key at Developers > API keys with 'Write' access to Customers only
1import os2import stripe34stripe.api_key = os.environ.get('STRIPE_SECRET_KEY')56# Confirm environment (live vs test)7try:8 customers = stripe.Customer.list(limit=1)9 mode = 'live' if not customers.data[0].livemode else 'test' if customers.data else 'unknown'10 print(f'Connected to Stripe {mode} mode')11except stripe.error.InvalidRequestError:12 # No customers yet is fine13 print('Stripe connected — no customers yet')Security notes
- •Store secret keys in environment variables — never in source code or git repositories
- •Use sk_test_* for development and sk_live_* only for production — they access completely separate data sets
- •Create Restricted API keys with only Customer read/write access for customer management automation
- •Customer management must run server-side — secret keys must never appear in frontend JavaScript
- •When logging for debugging, never log the full customer object if it contains PII (email, name, phone)
- •Rotate API keys immediately if you suspect exposure via Developers > API keys > Roll key
Key endpoints
/v1/customersCreates a new Stripe customer. Include email, name, and your internal user ID in metadata to enable cross-referencing between Stripe and your database.
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | optional | Customer's email address — used for receipts and customer search |
name | string | optional | Customer's full name — appears on invoices and receipts |
metadata | object | optional | Key-value pairs for internal tracking — user_id, plan tier, CRM ID, etc. Max 50 keys, 500 chars per value. |
phone | string | optional | Customer phone number for SMS receipts or authentication |
description | string | optional | An arbitrary description of the customer for your internal notes |
Request
1{"email": "alice@example.com", "name": "Alice Johnson", "metadata": {"user_id": "usr_abc123", "plan": "free", "signup_source": "organic"}}Response
1{"id": "cus_PqKLt2eZvKYlo2C", "object": "customer", "email": "alice@example.com", "name": "Alice Johnson", "created": 1706745700, "livemode": true, "metadata": {"user_id": "usr_abc123", "plan": "free"}, "subscriptions": {"data": []}, "default_source": null}/v1/customers/{id}Updates an existing customer. Use this to sync metadata changes from your application — plan upgrades, company name changes, or tagging customers for segmentation.
| Parameter | Type | Required | Description |
|---|---|---|---|
metadata | object | optional | Updated metadata key-value pairs. Set a key to empty string '' to delete it. Omitted keys are unchanged. |
email | string | optional | Updated email address |
invoice_settings.default_payment_method | string | optional | PaymentMethod ID (pm_*) to set as the default for future invoices and subscriptions |
Request
1{"metadata": {"plan": "pro", "company": "Acme Corp", "ltv": "2400"}}Response
1{"id": "cus_PqKLt2eZvKYlo2C", "metadata": {"user_id": "usr_abc123", "plan": "pro", "company": "Acme Corp", "ltv": "2400"}}/v1/customers/searchSearch customers by email, metadata, or other fields using Stripe's query syntax. More flexible than list but more aggressively rate-limited — do not use in hot loops.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | required | Stripe query string. Examples: 'email:"alice@example.com"', 'metadata["user_id"]:"usr_abc123"', 'name:"Alice"' |
limit | number | optional | Results per page, max 100 |
Response
1{"object": "search_result", "data": [{"id": "cus_PqKLt2eZvKYlo2C", "email": "alice@example.com", "metadata": {"user_id": "usr_abc123"}}], "has_more": false}/v1/payment_methods/{id}/attachAttaches a PaymentMethod (collected on the frontend via Stripe Elements or Checkout) to a customer. After attaching, set it as default via customer update.
| Parameter | Type | Required | Description |
|---|---|---|---|
customer | string | required | The customer ID (cus_*) to attach this payment method to |
Request
1{"customer": "cus_PqKLt2eZvKYlo2C"}Response
1{"id": "pm_1OqKLt2eZvKYlo2Cabc", "object": "payment_method", "type": "card", "card": {"brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2027}, "customer": "cus_PqKLt2eZvKYlo2C"}Step-by-step automation
Create a Customer on User Signup
Why: Creating a Stripe customer immediately on signup — before any payment — lets you attach payment methods, start trials, and track users in your billing system from day one.
When a user signs up in your application, create a corresponding Stripe customer and store the customer ID on your user record. Include your internal user ID in metadata for bi-directional lookup. Check for existing customers first to prevent duplicates from retry logic or race conditions.
1curl https://api.stripe.com/v1/customers \2 -u "$STRIPE_SECRET_KEY:" \3 -d email="alice@example.com" \4 -d name="Alice Johnson" \5 -d "metadata[user_id]"="usr_abc123" \6 -d "metadata[plan]"="free" \7 -d "metadata[signup_source]"="organic"Pro tip: Store the Stripe customer ID at the database level with a unique constraint. One user should map to exactly one Stripe customer. The duplicate-check pattern shown here protects against race conditions during signup.
Expected result: A Stripe customer ID (cus_*) created and ready for payment method attachment. The customer.id should be stored in your application database.
Sync Customer Metadata on Profile Changes
Why: Keeping metadata synchronized enables accurate segmentation, reporting, and dunning — your Stripe dashboard and reports should reflect your current user state.
When users upgrade plans, change their company name, or reach LTV milestones, update their Stripe customer metadata. PATCH /v1/customers/{id} merges metadata — omitted keys are preserved, and setting a key to an empty string '' deletes it. Never overwrite the entire metadata object unless you're resetting everything intentionally.
1# Update customer plan tier and company in metadata2curl https://api.stripe.com/v1/customers/cus_PqKLt2eZvKYlo2C \3 -u "$STRIPE_SECRET_KEY:" \4 -d "metadata[plan]"="pro" \5 -d "metadata[company]"="Acme Corp" \6 -d "metadata[ltv]"="2400"78# Delete a metadata key by setting it to empty string9curl https://api.stripe.com/v1/customers/cus_PqKLt2eZvKYlo2C \10 -u "$STRIPE_SECRET_KEY:" \11 -d "metadata[old_field]"=""Pro tip: Metadata values must be strings — convert numbers, booleans, and dates to strings before storing. Stripe metadata is searchable via the search API and visible in your Dashboard, making it valuable for support workflows.
Expected result: The customer's metadata in Stripe reflects the current state of your application's user record.
Attach and Set Default Payment Method
Why: A default payment method is required for subscription billing and allows one-click charging — setting it correctly prevents 'no payment source' errors.
After collecting a card via Stripe Elements or Checkout on the frontend (which returns a PaymentMethod ID), attach it server-side and set it as the customer's default. List existing payment methods first to avoid duplicates — the same card added twice creates two separate PaymentMethod records.
1# Attach payment method to customer2curl https://api.stripe.com/v1/payment_methods/pm_1OqKLt2eZvKYlo2Cabc/attach \3 -u "$STRIPE_SECRET_KEY:" \4 -d customer="cus_PqKLt2eZvKYlo2C"56# Set as default for invoices7curl https://api.stripe.com/v1/customers/cus_PqKLt2eZvKYlo2C \8 -u "$STRIPE_SECRET_KEY:" \9 -d "invoice_settings[default_payment_method]"="pm_1OqKLt2eZvKYlo2Cabc"Pro tip: Stripe returns the PaymentMethod ID to your server after the customer completes Stripe Elements or Checkout. Never pass raw card numbers to your server — only ever handle the pm_* token that Stripe.js creates.
Expected result: The payment method is attached to the customer and set as the default for future invoices and subscription billing.
Search and Segment Customers
Why: The customer search API enables targeted actions — finding customers on a specific plan, locating a user by email, or identifying customers needing dunning attention.
Use GET /v1/customers/search with Stripe's query syntax to find customers matching specific criteria. Search by email, name, or metadata fields. For large-scale segmentation (thousands of customers), use GET /v1/customers with pagination (starting_after cursor) instead of search to avoid hitting search rate limits.
1# Search by email2curl "https://api.stripe.com/v1/customers/search?query=email%3A%22alice%40example.com%22" \3 -u "$STRIPE_SECRET_KEY:"45# Search by metadata field6curl "https://api.stripe.com/v1/customers/search?query=metadata%5B%22plan%22%5D%3A%22pro%22&limit=100" \7 -u "$STRIPE_SECRET_KEY:"Pro tip: Cache the user_id → stripe_customer_id mapping in your application database. Use Stripe's search only as a fallback — it's slower and more rate-limited than a local database lookup.
Expected result: Matching customer objects retrieved for the query criteria, ready for bulk operations or targeted actions.
Complete working code
A complete customer lifecycle management module: create on signup with duplicate prevention, sync metadata on profile changes, attach payment methods, and look up customers. This is the foundation for any Stripe integration.
1import stripe2import os3import logging45logging.basicConfig(level=logging.INFO)6log = logging.getLogger(__name__)78stripe.api_key = os.environ['STRIPE_SECRET_KEY']910class StripeCustomerManager:1112 def get_or_create(self, user_id, email, name):13 # Check by metadata first (most reliable)14 existing = stripe.Customer.search(15 query=f'metadata["user_id"]:"{user_id}"'16 )17 if existing.data:18 log.info(f'Found existing customer {existing.data[0].id} for user {user_id}')19 return existing.data[0]2021 customer = stripe.Customer.create(22 email=email,23 name=name,24 metadata={'user_id': user_id, 'plan': 'free'}25 )26 log.info(f'Created customer {customer.id} for user {user_id}')27 return customer2829 def update_metadata(self, customer_id, updates):30 customer = stripe.Customer.modify(customer_id, metadata=updates)31 log.info(f'Updated {customer_id}: {list(updates.keys())}')32 return customer3334 def attach_card(self, customer_id, payment_method_id):35 pm = stripe.PaymentMethod.attach(payment_method_id, customer=customer_id)36 stripe.Customer.modify(37 customer_id,38 invoice_settings={'default_payment_method': payment_method_id}39 )40 log.info(f'Attached {pm.card.brand} **** {pm.card.last4} to {customer_id}')41 return pm4243 def get_by_user_id(self, user_id):44 results = stripe.Customer.search(45 query=f'metadata["user_id"]:"{user_id}"'46 )47 return results.data[0] if results.data else None4849 def list_payment_methods(self, customer_id):50 return stripe.PaymentMethod.list(customer=customer_id, type='card').data5152 def delete(self, customer_id):53 deleted = stripe.Customer.delete(customer_id)54 log.info(f'Deleted customer {customer_id}')55 return deleted565758# Usage59manager = StripeCustomerManager()6061# On user signup62customer = manager.get_or_create('usr_abc123', 'alice@example.com', 'Alice Johnson')63print(f'Customer ID: {customer.id}')6465# After plan upgrade66manager.update_metadata(customer.id, {'plan': 'pro', 'upgraded_at': '2024-02-01'})6768# After adding payment method via Stripe Elements69# manager.attach_card(customer.id, 'pm_from_frontend')Error handling
No such customer: 'cus_...'The most common cause: using a test mode customer ID with live mode keys, or vice versa. Test (cus_test_...) and live (cus_...) customers are completely separate and don't exist in the other environment. Also caused by a deleted customer.
Check that your STRIPE_SECRET_KEY matches the environment of the customer ID. Test mode keys (sk_test_*) only see test customers; live mode keys (sk_live_*) only see live customers. Verify the customer exists in your Stripe Dashboard.
No retry — fix the environment mismatch or create a new customer
Invalid email address format: '...'Email address provided doesn't pass Stripe's validation. Can happen with malformed addresses or Unicode characters.
Validate email format before calling the API. Stripe's validation is stricter than basic presence checks — run a standard email regex or use a validation library.
No retry — fix the email address
Payment method pm_... was not created by this account or has already been attachedThe PaymentMethod ID was created in a different Stripe account, is from the wrong environment, or has already been attached to another customer.
Confirm the pm_* ID was created by your Stripe account in the same environment (test/live). Each PaymentMethod can only be attached to one customer — if you need to move it, detach and reattach.
No retry — create a new PaymentMethod via Stripe Elements
Too many requestsRate limit exceeded. GET /v1/customers/search is more aggressively throttled than list endpoints. Can also occur during bulk customer creation or metadata updates.
Use GET /v1/customers with pagination (starting_after cursor) for bulk operations instead of search. For customer lookups in hot paths, cache the user_id → customer_id mapping in your database.
Exponential backoff: 1s, 2s, 4s, 8s. For search, switch to list + filter for bulk operations.
Metadata key '...' exceeds maximum length of 40 charactersStripe limits metadata keys to 40 characters and values to 500 characters.
Shorten metadata key names. Store longer data in your own database and use Stripe metadata for IDs and short flags only.
No retry — fix the metadata key/value length
Rate Limits for Stripe API
| Scope | Limit | Window |
|---|---|---|
| Live mode | 100 requests | per second |
| Test mode | 25 requests | per second |
| Customer search | More aggressive than list | per second — avoid in hot loops |
1import time2import stripe34def stripe_with_retry(fn, max_retries=5):5 for attempt in range(max_retries):6 try:7 return fn()8 except stripe.error.RateLimitError:9 wait = min(2 ** attempt, 32)10 print(f'Rate limited, retrying in {wait}s')11 time.sleep(wait)12 except stripe.error.APIConnectionError:13 if attempt == max_retries - 1: raise14 time.sleep(2 ** attempt)15 raise Exception('Max retries')- Cache the user_id → Stripe customer_id mapping in your own database — a single DB query is faster than a Stripe API call
- Use GET /v1/customers with cursor-based pagination for bulk customer operations instead of search
- Avoid calling GET /v1/customers/search in real-time request paths — use it for batch jobs only
- Batch metadata updates: if multiple fields change in one user action, send them in a single PATCH call
- Use Stripe's SDK auto-pagination (auto_paging_iter) for list endpoints to handle cursor management automatically
Security checklist
- Store STRIPE_SECRET_KEY in environment variables — never in frontend code, mobile apps, or git repositories
- Customer management must run server-side only — never expose secret key operations to the browser
- Never pass raw card numbers to your server — use Stripe Elements or Checkout to get PaymentMethod IDs (pm_*)
- Verify you're in the correct Stripe environment (test vs live) before any customer operation to avoid data contamination
- Log customer operations with customer IDs but redact PII (email, name) from logs per your data privacy policy
- When deleting customer data for GDPR/CCPA compliance, call DELETE /v1/customers/{id} — Stripe purges the PII
- Use Restricted API keys with only Customer read/write access for customer management automation
- Validate that customer IDs in your API requests originate from authenticated users — prevent IDOR vulnerabilities
Automation use cases
User Sync on Signup
beginnerCreate a Stripe customer immediately when a user registers, storing the customer ID in your database for instant billing readiness.
CRM Metadata Sync
intermediateSync customer LTV, deal stage, and company data from your CRM into Stripe metadata to enable plan-based segmentation in billing reports.
GDPR Deletion Workflow
beginnerOn account deletion request, call DELETE /v1/customers/{id} to purge Stripe PII, then remove the customer ID from your database.
Payment Method Expiry Monitoring
intermediateList all customers' payment methods, identify cards expiring in the next 30 days, and send proactive update reminders to reduce subscription churn from card failures.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Free tier (100 tasks/month), paid from $19.99/monthZapier's Stripe integration can create customers and update metadata in response to triggers from other apps (new Typeform submission, new HubSpot contact) without code.
- + No code required
- + 200+ trigger apps for customer creation
- + Quick setup
- - No customer search support
- - Limited metadata control
- - Each operation costs a task
Make
Free tier (1,000 ops/month), paid from $9/monthMake's Stripe module supports customer creation, update, and search with more control than Zapier, including conditional logic based on customer attributes.
- + Visual workflow builder
- + Better conditional logic than Zapier
- + Customer search support
- - More complex setup than Zapier
- - Monthly operation limits
- - Less documentation than native API
n8n
Free (self-hosted), Cloud from €20/monthn8n's HTTP Request node accesses all Stripe customer endpoints with full control over metadata, payment methods, and search queries.
- + Full Stripe Customer API access
- + Self-hostable
- + Webhook triggers for real-time sync
- - No native Stripe module (HTTP Request)
- - Technical setup required
- - Infrastructure management for self-hosted
Best practices
- Create exactly one Stripe customer per user and store the customer ID in your database — never create duplicate customers for the same user
- Store your internal user ID in customer metadata immediately on creation to enable two-way lookup without relying on email (which can change)
- Always verify the test/live mode environment matches before customer operations — 'No such customer' is almost always a mode mismatch
- Use the search API as a fallback, not a primary lookup path — your own database is faster and not rate-limited
- Set the default payment method explicitly after attaching it — Stripe does not automatically set the first attached card as default
- For GDPR/CCPA compliance, call DELETE /v1/customers/{id} to purge PII — this deletes email, name, and contact info from Stripe's servers
- Keep metadata values as strings and limit to short IDs and flags — store complex data in your database and link via ID
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
I'm building Stripe customer management in Python. I create customers with stripe.Customer.create(), update metadata with stripe.Customer.modify(), and look up by user ID using stripe.Customer.search(). Help me: (1) add a get_or_create function that prevents duplicate customers using search before create, (2) implement a sync_metadata function that merges new metadata without overwriting existing keys, (3) add exponential backoff for 429 rate limit errors, and (4) explain the test vs live mode isolation issue and how to detect which environment I'm connected to.
Build a Stripe customer management admin panel in React with: (1) a search box to find customers by email or metadata user_id via a backend API endpoint, (2) a customer detail card showing email, name, plan from metadata, created date, and number of subscriptions, (3) a 'Payment Methods' section showing saved cards with brand, last4, and expiry, (4) an 'Edit Metadata' form to update plan tier and company name, (5) a 'Delete Customer' button with confirmation. Backend endpoints: GET /api/customers/search?q=, GET /api/customers/:id, PATCH /api/customers/:id/metadata, DELETE /api/customers/:id.
Frequently asked questions
Why am I getting 'No such customer' even though the customer exists in my Stripe Dashboard?
This almost always means a test/live mode mismatch. Stripe completely isolates test and live mode data. If you're using sk_live_* keys but the customer was created with sk_test_* keys (or vice versa), the customer doesn't exist in the other environment. Check your STRIPE_SECRET_KEY environment variable and verify you're in the right mode. In the Stripe Dashboard, toggle the 'Test mode' switch to see which environment the customer is in.
Is the Stripe Customer API free to use?
Yes. Creating and managing Stripe customers has no cost — you only pay transaction fees (2.9% + $0.30 per card charge) when actual payments occur. The API itself, including customer creation, metadata updates, and payment method management, is free.
How many metadata keys can I store on a customer?
Stripe allows up to 50 metadata key-value pairs per customer. Keys are limited to 40 characters, values to 500 characters. For more complex data, store it in your own database and use Stripe metadata only for short identifiers and flags (plan tier, user ID, company ID).
What happens when I delete a Stripe customer?
Stripe marks the customer as deleted and purges their PII (email, name, phone, address). Historical payment records are preserved for financial compliance but the customer can no longer be charged. Subscriptions attached to deleted customers are also canceled. This is the correct path for GDPR/CCPA right-to-erasure requests.
What happens when I hit the rate limit?
Stripe returns a 429 Too Many Requests error with a Retry-After header. In the Stripe SDK, this is a RateLimitError. The customer search endpoint is more aggressively limited than list endpoints — if you're searching in a loop (e.g., looking up customers during a sync job), switch to paginating GET /v1/customers instead and filter locally.
Can I have multiple payment methods per customer?
Yes. A customer can have multiple attached PaymentMethods — multiple cards, bank accounts, etc. List them with GET /v1/customers/{id}/payment_methods. The default_payment_method on the customer's invoice_settings determines which one is used for subscriptions. You can let users choose their preferred card by updating that field.
Can RapidDev help build a custom billing system with Stripe customer management?
Yes. RapidDev has built 600+ apps including complete SaaS billing systems, subscription portals, and customer management dashboards integrated with Stripe. If you need a custom Stripe integration, get a free consultation at rapidevelopers.com.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation