integrating-stripe-webhooks
About
This skill helps developers fix Stripe webhook signature verification errors caused by missing raw request bodies, providing framework-specific parsing solutions. It also addresses common TypeScript type mismatches like subscription period field errors. Use it when implementing webhook endpoints and encountering "Raw body not available" or related 400/404 errors.
Quick Install
Claude Code
Recommended/plugin add https://github.com/pr-pm/prpmgit clone https://github.com/pr-pm/prpm.git ~/.claude/skills/integrating-stripe-webhooksCopy and paste this command in Claude Code to install this skill
Documentation
Integrating Stripe Webhooks
Overview
Stripe webhooks require raw request bodies for signature verification. Most web frameworks parse JSON automatically, breaking verification. This skill provides framework-specific solutions for the raw body problem and documents common TypeScript type mismatches.
When to Use
Use this skill when:
- Getting "Raw body not available" errors from Stripe webhooks
- Webhook signature verification fails with 400 errors
- Implementing new Stripe webhook endpoints
- Getting
TypeError: Cannot read property 'current_period_start'from subscription events - Webhooks return 404 (route registration issues)
Don't use for:
- General Stripe API integration (not webhooks)
- Frontend Stripe Elements implementation
- Stripe checkout session creation (use Stripe docs)
Quick Reference
| Problem | Solution |
|---|---|
| Raw body not available | Configure custom body parser (see framework examples) |
| Signature verification fails | Use raw body bytes/buffer, not parsed JSON |
| 404 on webhook endpoint | Register webhook route inside API prefix |
current_period_start undefined | Access from subscription.items.data[0] not root |
| URI validation errors | URL-encode dynamic parameters with encodeURIComponent() |
Critical: Raw Body Parsing
THE PROBLEM: Stripe's constructEvent() requires the exact bytes received to verify the signature. JSON parsing modifies the body, breaking verification.
THE SOLUTION: Access raw body before any parsing middleware.
Framework Examples
Node.js - Fastify (most common for new projects):
// In main server file, BEFORE registering routes
server.addContentTypeParser('application/json',
{ parseAs: 'buffer' },
async (req: any, body: Buffer) => {
req.rawBody = body; // Store for webhooks
return JSON.parse(body.toString('utf8')); // Parse for other routes
}
);
// In webhook handler
const rawBody = (request as any).rawBody;
const event = stripe.webhooks.constructEvent(
rawBody, signature, webhookSecret
);
Node.js - Express:
// Define webhook route BEFORE express.json() middleware
app.post('/webhooks/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
const event = stripe.webhooks.constructEvent(
req.body, // Already raw Buffer
req.headers['stripe-signature'],
webhookSecret
);
}
);
app.use(express.json()); // After webhook route
Python - FastAPI:
@app.post('/webhooks/stripe')
async def stripe_webhook(request: Request):
payload = await request.body() # Use .body() not .json()
signature = request.headers.get('stripe-signature')
event = stripe.Webhook.construct_event(
payload, signature, webhook_secret
)
General Pattern: Get raw bytes/buffer → verify signature → use parsed event from Stripe.
Common Mistakes
1. Subscription Period Fields Missing
Error: TypeError: Cannot read property 'current_period_start' of undefined
Cause: Stripe returns period dates in subscription.items.data[0], not at subscription root. TypeScript types don't include these fields on SubscriptionItem.
Fix:
// ❌ WRONG - fields don't exist here
new Date(subscription.current_period_start * 1000)
// ✅ CORRECT - get from first subscription item
const firstItem = subscription.items.data[0] as any;
const periodStart = firstItem?.current_period_start || subscription.billing_cycle_anchor;
const periodEnd = firstItem?.current_period_end || subscription.billing_cycle_anchor;
await updateOrg({
start_date: new Date(periodStart * 1000),
end_date: new Date(periodEnd * 1000),
});
2. Route Not Found (404)
Cause: Webhook routes registered outside API prefix.
// ❌ WRONG - creates /webhooks/stripe instead of /api/v1/webhooks/stripe
export async function registerRoutes(server) {
server.register(async (api) => {
await api.register(subscriptionRoutes, { prefix: '/subscriptions' });
}, { prefix: '/api/v1' });
await server.register(webhookRoutes, { prefix: '/webhooks' }); // Outside!
}
// ✅ CORRECT - inside API prefix
export async function registerRoutes(server) {
server.register(async (api) => {
await api.register(subscriptionRoutes, { prefix: '/subscriptions' });
await api.register(webhookRoutes, { prefix: '/webhooks' }); // Inside
}, { prefix: '/api/v1' });
}
3. URL Encoding in Checkout URLs
Error: "body/successUrl must match format 'uri'"
Cause: Organization names or parameters with spaces not URL-encoded.
// ❌ WRONG - "Broke Org" creates invalid URL
const successUrl = `${origin}/orgs?name=${orgName}&subscription=success`;
// ✅ CORRECT - encode dynamic parameters
const successUrl = `${origin}/orgs?name=${encodeURIComponent(orgName)}&subscription=success`;
Implementation Checklist
Server Setup:
- Configure raw body parser BEFORE routes
- Register webhook routes inside API prefix (if using one)
- Set
STRIPE_WEBHOOK_SECRETenvironment variable - Verify webhook secret is configured before processing
Webhook Handler:
- Validate
stripe-signatureheader exists - Access raw body (not parsed JSON)
- Use
stripe.webhooks.constructEvent()for verification - Handle
SignatureVerificationErrorseparately - Return 200 for received events (even if processing fails)
- Log all events with ID and type
Subscription Events:
- Get period dates from
subscription.items.data[0] - Cast to
anyto access TypeScript-missing fields - Fallback to
billing_cycle_anchorif items missing - Store
org_idin subscription metadata - Update verification status based on subscription status
Frontend:
- URL-encode all dynamic parameters
- URL-encode organization names in success/cancel URLs
- Handle checkout errors gracefully
- Poll for verification after checkout success
Testing Locally
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Forward webhooks to local server
stripe listen --forward-to localhost:3000/api/v1/webhooks/stripe
# Trigger test events
stripe trigger customer.subscription.created
stripe trigger customer.subscription.updated
stripe trigger invoice.paid
Real-World Impact
Before applying these patterns:
- Webhooks fail with 400 "Invalid signature"
- Subscription updates crash with undefined property errors
- Hours debugging TypeScript type mismatches
- Checkout fails with URL validation errors
After applying:
- Webhooks verify successfully
- Subscription data extracts correctly
- Type-safe with explicit casting
- Checkout URLs work with any organization name
References
- Stripe Webhook Signature Verification
- Stripe Subscription Object
- See framework documentation for body parsing middleware
GitHub Repository
Related Skills
sglang
MetaSGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
llamaguard
OtherLlamaGuard is Meta's 7-8B parameter model for moderating LLM inputs and outputs across six safety categories like violence and hate speech. It offers 94-95% accuracy and can be deployed using vLLM, Hugging Face, or Amazon SageMaker. Use this skill to easily integrate content filtering and safety guardrails into your AI applications.
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
