Stripe API Migrate Subscriptions
Technical deep dive into programmatic Stripe subscription migration. Learn API endpoints, rate limits, error handling, and best practices for automated migrations.
# Quick example: Migrate a subscription
curl -X POST https://api.submigrations.com/v1/migrations \
-H "Authorization: Bearer YOUR_API_KEY" \
-d source_account="acct_source" \
-d destination_account="acct_destination" \
-d subscription_id="sub_123"
Authentication & Security
All API requests must be authenticated using OAuth 2.0 or API keys. We recommend OAuth for production applications.
API Key Authentication
// Node.js example
const submigrations = require('@submigrations/node');
const client = submigrations.Client({
apiKey: process.env.SUBMIGRATIONS_API_KEY,
stripeSecretKey: process.env.STRIPE_SECRET_KEY
});
OAuth 2.0 Flow
// OAuth authorization URL
https://api.submigrations.com/oauth/authorize?
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_CALLBACK_URL&
response_type=code&
scope=read:migrations+write:migrations
API Reference
Create Migration
POST /v1/migrations
Request Body
{
"source_account": "acct_1234567890",
"destination_account": "acct_0987654321",
"resources": {
"products": true,
"prices": true,
"customers": true,
"subscriptions": true,
"coupons": true
},
"options": {
"preserve_ids": false,
"preserve_billing_cycles": true,
"test_mode": false
}
}
Response
{
"id": "mig_1234567890",
"status": "pending",
"created": 1234567890,
"estimated_duration": 300,
"resource_counts": {
"products": 45,
"subscriptions": 1250,
"customers": 1100
}
}
Get Migration Status
GET /v1/migrations/{migration_id}
Response
{
"id": "mig_1234567890",
"status": "in_progress",
"progress": {
"percentage": 45,
"current_step": "migrating_subscriptions",
"completed_resources": {
"products": 45,
"prices": 120,
"customers": 500
},
"remaining_resources": {
"subscriptions": 750
}
},
"logs": [
{
"timestamp": 1234567890,
"level": "info",
"message": "Successfully migrated 500 customers"
}
]
}
List Migrations
GET /v1/migrations
Query Parameters
status
- Filter by status (pending, in_progress, completed, failed)limit
- Number of results (1-100, default: 10)starting_after
- Cursor for pagination
Rate Limits & Performance
Rate limits are enforced to ensure fair usage and platform stability. Plan accordingly for large migrations.
Endpoint | Rate Limit | Burst |
---|---|---|
Create Migration | 10 req/hour | 20 req |
Get Status | 100 req/min | 200 req |
List Migrations | 60 req/min | 100 req |
Handling Rate Limits
// Exponential backoff example
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.statusCode === 429) {
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
Error Handling & Recovery
Error Response Format
{
"error": {
"type": "invalid_request_error",
"message": "Source account not found",
"code": "account_not_found",
"param": "source_account",
"request_id": "req_1234567890"
}
}
Common Error Codes
Code | Description | Recovery Action |
---|---|---|
invalid_api_key | API key is invalid or expired | Check API key configuration |
insufficient_permissions | API key lacks required permissions | Update Stripe API key permissions |
migration_in_progress | Another migration is already running | Wait for completion or cancel existing |
resource_limit_exceeded | Too many resources to migrate | Use batched migration approach |
Idempotency
Use idempotency keys to safely retry requests without creating duplicates:
curl -X POST https://api.submigrations.com/v1/migrations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: unique-key-123" \
-d source_account="acct_source" \
-d destination_account="acct_destination"
Webhooks & Events
Webhook Configuration
// Configure webhook endpoint
POST /v1/webhook_endpoints
{
"url": "https://your-app.com/webhooks/submigrations",
"enabled_events": [
"migration.started",
"migration.progress",
"migration.completed",
"migration.failed"
]
}
Event Types
migration.started
Fired when migration begins processing
migration.progress
Periodic updates during migration (every 10%)
migration.completed
Migration finished successfully
migration.failed
Migration encountered unrecoverable error
Webhook Security
// Verify webhook signature
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Code Examples
Node.js Full Migration Example
const Submigrations = require('@submigrations/node');
const client = new Submigrations(process.env.SUBMIGRATIONS_API_KEY);
async function migrateStripeAccounts() {
try {
// Create migration
const migration = await client.migrations.create({
source_account: 'acct_source',
destination_account: 'acct_destination',
resources: {
products: true,
prices: true,
customers: true,
subscriptions: true,
coupons: true
},
options: {
preserve_billing_cycles: true,
test_mode: true // Run in test mode first
}
});
console.log(`Migration started: ${migration.id}`);
// Poll for status
let status = migration.status;
while (status === 'pending' || status === 'in_progress') {
await new Promise(resolve => setTimeout(resolve, 5000));
const update = await client.migrations.retrieve(migration.id);
status = update.status;
console.log(`Progress: ${update.progress.percentage}%`);
}
if (status === 'completed') {
console.log('Migration completed successfully!');
// Download detailed report
const report = await client.migrations.downloadReport(migration.id);
await fs.writeFileSync('migration-report.json', report);
} else {
console.error('Migration failed:', migration.error);
}
} catch (error) {
console.error('Error:', error.message);
}
}
migrateStripeAccounts();
Python Batched Migration
import submigrations
import time
client = submigrations.Client(api_key=os.environ['SUBMIGRATIONS_API_KEY'])
def migrate_in_batches(source, destination, batch_size=100):
"""Migrate subscriptions in batches for large accounts"""
# Get total count
subscriptions = client.analyze_account(source)
total = subscriptions['counts']['subscriptions']
# Process in batches
for offset in range(0, total, batch_size):
migration = client.migrations.create(
source_account=source,
destination_account=destination,
resources={
'subscriptions': {
'limit': batch_size,
'offset': offset
}
}
)
# Wait for batch completion
while migration.status in ['pending', 'in_progress']:
time.sleep(5)
migration = client.migrations.retrieve(migration.id)
if migration.status == 'failed':
raise Exception(f"Batch failed: {migration.error}")
print(f"Completed batch {offset}-{offset+batch_size}")
print("All batches completed successfully")
# Run migration
migrate_in_batches('acct_source', 'acct_destination')
CLI Usage
# Install CLI
npm install -g @submigrations/cli
# Configure authentication
submigrations auth login
# Run migration with progress bar
submigrations migrate \
--source acct_source \
--destination acct_destination \
--resources all \
--preserve-billing-cycles \
--output migration-report.json
# Check status of running migration
submigrations status mig_1234567890
# List recent migrations
submigrations list --limit 10
Best Practices
✓ Do's
- • Always test in Stripe test mode first
- • Use idempotency keys for all requests
- • Implement proper error handling and retries
- • Monitor webhook events for real-time updates
- • Batch large migrations to avoid timeouts
- • Verify data integrity post-migration
- • Keep audit logs for compliance
✗ Don'ts
- • Don't hardcode API keys in your code
- • Don't ignore rate limits
- • Don't skip test migrations
- • Don't migrate during peak traffic
- • Don't forget to update webhook endpoints
- • Don't assume immediate completion
- • Don't delete source data immediately
Open Source Examples
Explore our GitHub repository for complete examples, SDKs, and community contributions.