integrating-stripe-webhooks

Use when implementing Stripe webhook endpoints and getting 'Raw body not available' or signature verification errors - provides raw body parsing solutions and subscription period field fixes across frameworks

153 stars

Best use case

integrating-stripe-webhooks is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when implementing Stripe webhook endpoints and getting 'Raw body not available' or signature verification errors - provides raw body parsing solutions and subscription period field fixes across frameworks

Teams using integrating-stripe-webhooks should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/integrating-stripe-webhooks/SKILL.md --create-dirs "https://raw.githubusercontent.com/Microck/ordinary-claude-skills/main/skills_all/integrating-stripe-webhooks/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/integrating-stripe-webhooks/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How integrating-stripe-webhooks Compares

Feature / Agentintegrating-stripe-webhooksStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when implementing Stripe webhook endpoints and getting 'Raw body not available' or signature verification errors - provides raw body parsing solutions and subscription period field fixes across frameworks

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 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):

```typescript
// 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**:

```javascript
// 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**:

```python
@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:**
```typescript
// ❌ 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.

```typescript
// ❌ 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.

```typescript
// ❌ 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_SECRET` environment variable
- [ ] Verify webhook secret is configured before processing

**Webhook Handler:**
- [ ] Validate `stripe-signature` header exists
- [ ] Access raw body (not parsed JSON)
- [ ] Use `stripe.webhooks.constructEvent()` for verification
- [ ] Handle `SignatureVerificationError` separately
- [ ] 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 `any` to access TypeScript-missing fields
- [ ] Fallback to `billing_cycle_anchor` if items missing
- [ ] Store `org_id` in 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

```bash
# 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](https://stripe.com/docs/webhooks/signatures)
- [Stripe Subscription Object](https://stripe.com/docs/api/subscriptions/object)
- See framework documentation for body parsing middleware

Related Skills

stripe-integration

153
from Microck/ordinary-claude-skills

Implement Stripe payment processing for robust, PCI-compliant payment flows including checkout, subscriptions, and webhooks. Use when integrating Stripe payments, building subscription systems, or implementing secure checkout flows.

nextjs-stripe-integration

153
from Microck/ordinary-claude-skills

Add Stripe payment processing to Next.js projects. Implement checkout sessions, payment handling, subscriptions, webhooks, and customer management. Use when adding Stripe to a Next.js project, building payment flows, implementing subscriptions, or integrating payment processing.

laravel-cashier-stripe

153
from Microck/ordinary-claude-skills

Laravel Cashier (Stripe) - Subscription billing and payment processing

zapier-workflows

153
from Microck/ordinary-claude-skills

Manage and trigger pre-built Zapier workflows and MCP tool orchestration. Use when user mentions workflows, Zaps, automations, daily digest, research, search, lead tracking, expenses, or asks to "run" any process. Also handles Perplexity-based research and Google Sheets data tracking.

writing-skills

153
from Microck/ordinary-claude-skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dependencies, and rust-documentation skills.

writing-plans

153
from Microck/ordinary-claude-skills

Use when design is complete and you need detailed implementation tasks for engineers with zero codebase context - creates comprehensive implementation plans with exact file paths, complete code examples, and verification steps assuming engineer has minimal domain knowledge

workflow-orchestration-patterns

153
from Microck/ordinary-claude-skills

Design durable workflows with Temporal for distributed systems. Covers workflow vs activity separation, saga patterns, state management, and determinism constraints. Use when building long-running processes, distributed transactions, or microservice orchestration.

workflow-management

153
from Microck/ordinary-claude-skills

Create, debug, or modify QStash workflows for data updates and social media posting in the API service. Use when adding new automated jobs, fixing workflow errors, or updating scheduling logic.

workflow-interactive-dev

153
from Microck/ordinary-claude-skills

用于开发 FastGPT 工作流中的交互响应。详细说明了交互节点的架构、开发流程和需要修改的文件。

woocommerce-dev-cycle

153
from Microck/ordinary-claude-skills

Run tests, linting, and quality checks for WooCommerce development. Use when running tests, fixing code style, or following the development workflow.

woocommerce-code-review

153
from Microck/ordinary-claude-skills

Review WooCommerce code changes for coding standards compliance. Use when reviewing code locally, performing automated PR reviews, or checking code quality.

Wheels Migration Generator

153
from Microck/ordinary-claude-skills

Generate database-agnostic Wheels migrations for creating tables, altering schemas, and managing database changes. Use when creating or modifying database schema, adding tables, columns, indexes, or foreign keys. Prevents database-specific SQL and ensures cross-database compatibility.