| name | stripe-api-integration |
| description | Use when working with Stripe API fundamentals, webhooks, authentication, error handling, CLI, or testing. Invoke for webhook implementation, signature verification, API error patterns, idempotency, rate limiting, Stripe CLI usage, or test mode setup. |
| allowed-tools | Read, Grep, Glob |
Stripe API Integration Expert
Purpose
Expert knowledge of Stripe API fundamentals and integration patterns. Covers webhooks, authentication, error handling, API best practices, Stripe CLI, testing strategies, and common integration patterns.
When to Use
Invoke this skill when:
- Setting up webhook endpoints
- Verifying webhook signatures
- Handling API errors
- Implementing idempotency
- Using the Stripe CLI
- Testing Stripe integrations
- Managing API keys
- Handling rate limits
- Debugging API calls
- Understanding API versioning
Documentation Available
Location: /Users/zach/Documents/cc-skills/docs/stripe/
Coverage (~920 files in api/ + webhooks/ + cli/):
Related Skills
- stripe-payments: For payment processing
- stripe-billing-subscriptions: For subscription events
- stripe-connect: For Connect webhooks
Best Practices
- Verify webhook signatures - Always verify to prevent tampering
- Handle webhooks idempotently - Events may be delivered multiple times
- Use API versioning - Pin to specific version for stability
- Implement proper error handling - Handle all error types
- Use idempotency keys - Prevent duplicate operations
- Monitor webhook endpoints - Ensure they're responding quickly (<5s)
- Test with Stripe CLI - Local webhook testing
- Use test mode extensively - Before going live
- Log API requests - For debugging and auditing
- Handle rate limits - Implement backoff strategies
Examples
Example 1: Webhook Implementation & Signature Verification
Question: "How do I properly implement webhooks?"
Response:
import { buffer } from 'micro';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const buf = await buffer(req);
const sig = req.headers['stripe-signature'];
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(buf, sig!, webhookSecret);
} (err) {
.(, err.);
res.().();
}
eventId = event.;
existingEvent = db..({
: { : eventId },
});
(existingEvent) {
.(, eventId);
res.({ : });
}
{
(event);
db..({
: {
: eventId,
: event.,
: (),
},
});
res.({ : });
} (err) {
.(, err);
res.().({ : });
}
}
() {
.(, event.);
(event.) {
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
.();
}
}
() {
.(, paymentIntent.);
db..({
: { : paymentIntent. },
: {
: ,
: (),
},
});
({
: paymentIntent.!,
: ,
: ,
: { paymentIntent },
});
}
Get Webhook Secret:
stripe listen --print-secret
References:
- See:
docs/stripe/webhooks/
Example 2: Error Handling
Question: "How do I handle Stripe API errors?"
Response:
import Stripe from 'stripe';
async function handleStripeOperation() {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: 1000,
currency: 'usd',
});
return { success: true, paymentIntent };
} catch (error) {
if (error instanceof Stripe.errors.StripeError) {
switch (error.type) {
case 'StripeCardError':
console.error('Card error:', error.message);
return {
success: false,
error: 'Your card was declined',
code: error.code,
};
case 'StripeRateLimitError':
console.error('Rate limit exceeded');
{
: ,
: ,
};
:
.(, error.);
{
: ,
: ,
: error.,
};
:
.(, error.);
{
: ,
: ,
};
:
.(, error.);
{
: ,
: ,
};
:
.(, error.);
{
: ,
: ,
};
:
.(, error);
{
: ,
: ,
};
}
}
.(, error);
{
: ,
: ,
};
}
}
retryWithBackoff<T>(
: <T>,
maxRetries =
): <T> {
: ;
( i = ; i < maxRetries; i++) {
{
();
} (error) {
lastError = error;
(error ..) {
(
error. === ||
error. ===
) {
error;
}
}
delay = .(, i) * ;
.();
( (resolve, delay));
}
}
lastError!;
}
result = (
stripe..({
: ,
: ,
})
);
Common Error Codes:
card_declined - Card was declined
insufficient_funds - Not enough funds
expired_card - Card expired
incorrect_cvc - Wrong CVC
processing_error - Processing error
rate_limit - Too many requests
References:
- See:
docs/stripe/api/errors/
Example 3: Idempotency
Question: "How do I prevent duplicate charges?"
Response:
async function createPaymentWithIdempotency(orderId: string, amount: number) {
const idempotencyKey = `order_${orderId}`;
try {
const paymentIntent = await stripe.paymentIntents.create(
{
amount: amount * 100,
currency: 'usd',
metadata: { orderId },
},
{
idempotencyKey,
}
);
return paymentIntent;
} catch (error) {
if (
error instanceof Stripe.errors.StripeError &&
error.type === 'StripeIdempotencyError'
) {
console.error('Idempotency key already used with different parameters');
}
throw error;
}
}
function generateIdempotencyKey(
operation: string,
resourceId: string,
timestamp?: number
): {
ts = timestamp || .();
;
}
() {
idempotencyKey = (
,
customerId
);
subscription = stripe..(
{
: customerId,
: [{ : priceId }],
},
{ idempotencyKey }
);
subscription;
}
() {
idempotencyKey = ;
existingPayment = db..({
: { idempotencyKey },
});
(existingPayment) {
.();
existingPayment;
}
paymentIntent = stripe..(
{
: amount * ,
: ,
: { orderId },
},
{ idempotencyKey }
);
db..({
: {
: paymentIntent.,
orderId,
amount,
idempotencyKey,
: (),
},
});
paymentIntent;
}
Idempotency Key Rules:
- Must be unique per operation
- Valid for 24 hours
- Only for POST requests
- Returns same result for duplicate requests
References:
- See:
docs/stripe/api/idempotent_requests/
Example 4: Stripe CLI Usage
Question: "How do I use the Stripe CLI?"
Response:
brew install stripe/stripe-cli/stripe
stripe login
stripe listen --forward-to localhost:3000/api/webhooks
stripe listen --print-secret
stripe trigger payment_intent.succeeded
stripe trigger customer.subscription.created
stripe trigger invoice.payment_failed
stripe listen --forward-to localhost:3000/api/webhooks \
--events payment_intent.succeeded,customer.subscription.updated
stripe logs tail
stripe logs tail --filter-type=payment_intent
stripe payment_intents create \
--amount=1000 \
--currency=usd
stripe customers list --limit=10
stripe subscriptions retrieve sub_xxxxx
stripe trigger payment_intent.succeeded \
--add payment_intent:amount=5000
stripe listen \
--forward-to localhost:3000/api/webhooks/payments \
--events payment_intent.succeeded,charge.succeeded \
--forward-to localhost:3000/api/webhooks/subscriptions \
--events customer.subscription.created,customer.subscription.updated
docker run --rm -it stripe/stripe-cli:latest \
listen --api-key sk_test_xxx \
--forward-to host.docker.internal:3000/api/webhooks
References:
Example 5: Testing Strategies
Question: "How do I test my Stripe integration?"
Response:
const stripe = new Stripe(
process.env.NODE_ENV === 'production'
? process.env.STRIPE_SECRET_KEY!
: process.env.STRIPE_TEST_SECRET_KEY!
);
const TEST_CARDS = {
success: '4242424242424242',
decline: '4000000000000002',
insufficientFunds: '4000000000009995',
requires3DS: '4000002500003155',
requiresAuth: '4000002760003184',
};
async function testPaymentScenarios() {
const successPayment = await stripe.paymentIntents.create({
amount: 1000,
currency: 'usd',
payment_method_data: {
type: 'card',
card: { token: 'tok_visa' },
},
confirm: true,
});
console.log('Success:', successPayment.);
{
stripe..({
: ,
: ,
: {
: ,
: { : },
},
: ,
});
} (error) {
.(, error.);
}
}
() {
testClock = stripe...({
: .(.() / ),
: ,
});
customer = stripe..({
: ,
: testClock.,
});
subscription = stripe..({
: customer.,
: [{ : }],
: ,
});
stripe...(testClock., {
: .(.() / ) + * * * ,
});
updatedSubscription = stripe..(
subscription.
);
.(, updatedSubscription.);
stripe...(testClock.);
}
{ jest } ;
jest.(, {
jest.().( ({
: {
: jest.().({
: ,
: ,
: ,
: ,
}),
: jest.().({
: ,
: ,
}),
},
: {
: jest.().({
: ,
: ,
}),
},
}));
});
(, {
(, () => {
paymentIntent = stripe..({
: ,
: ,
});
(paymentIntent.).();
(paymentIntent.).();
});
(, () => {
(
stripe..({
: ,
: ,
: {
: ,
: { : },
},
: ,
})
)..();
});
});
Test Card Numbers:
4242424242424242 - Success
4000000000000002 - Declined
4000000000009995 - Insufficient funds
4000002500003155 - Requires 3D Secure
4000000000000341 - Attaches and charges
References:
- See:
docs/stripe/testing/
Common Patterns
Pagination
async function listAllCustomers() {
let customers: Stripe.Customer[] = [];
let hasMore = true;
let startingAfter: string | undefined;
while (hasMore) {
const page = await stripe.customers.list({
limit: 100,
starting_after: startingAfter,
});
customers = customers.concat(page.data);
hasMore = page.has_more;
startingAfter = page.data[page.data.length - 1]?.id;
}
return customers;
}
Expand Related Objects
const invoice = await stripe.invoices.retrieve('in_xxx', {
expand: ['customer', 'subscription', 'payment_intent'],
});
console.log(invoice.customer.email);
console.log(invoice.subscription.items);
Rate Limit Handling
async function handleRateLimit<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
if (
error instanceof Stripe.errors.StripeError &&
error.type === 'StripeRateLimitError'
) {
await new Promise(resolve => setTimeout(resolve, 1000));
return handleRateLimit(fn);
}
throw error;
}
}
Search Helpers
grep -r "API\|endpoint\|request" /Users/zach/Documents/cc-skills/docs/stripe/api/
grep -r "webhook\|event\|signature" /Users/zach/Documents/cc-skills/docs/stripe/webhooks/
grep -r "CLI\|stripe listen\|trigger" /Users/zach/Documents/cc-skills/docs/stripe/cli/
ls /Users/zach/Documents/cc-skills/docs/stripe/api/
Common Errors
-
Webhook signature verification failed: Wrong secret or body modified
- Solution: Use raw body and correct webhook secret
-
Idempotency key mismatch: Same key with different parameters
- Solution: Use unique key per operation or retrieve original
-
Rate limit exceeded: Too many requests
- Solution: Implement exponential backoff
-
API version mismatch: Using features from newer version
- Solution: Pin API version or upgrade
API Best Practices
- Always verify webhooks - Security critical
- Handle idempotency - Prevent duplicates
- Use expand sparingly - Only when needed
- Implement retry logic - For transient errors
- Log API calls - For debugging
- Use test mode - Extensively before production
- Pin API version - Avoid breaking changes
- Monitor webhook health - Response time and success rate
- Handle all error types - Graceful degradation
- Use metadata - Track custom data
Notes
- Documentation covers latest Stripe API (2023+)
- Webhooks are the reliable way to track events
- Always verify webhook signatures
- Idempotency keys prevent duplicate operations
- Test mode has same features as live mode
- Stripe CLI essential for local development
- File paths reference local documentation cache
- For latest updates, check https://stripe.com/docs/api