Claude Code Paddle Billing Integration Setup Guide
Integrating billing into your application doesn’t have to be a nightmare. Paddle, a merchant of record platform, simplifies subscription management and payment processing. Combined with Claude Code’s AI-assisted development capabilities, you can set up a robust billing system in hours rather than days.
This guide walks you through integrating Paddle billing using Claude Code, covering everything from initial setup to handling webhooks and managing subscriptions.
Defining a Paddle Billing Skill
Before writing integration code, you can define a Claude skill that encapsulates the entire billing workflow. Skills give Claude a focused persona and explicit instructions for handling events:
---
name: paddle-billing
description: Handles Paddle billing events and manages subscription workflows
---
# Paddle Billing Workflow Handler
You handle incoming Paddle webhook events and execute appropriate billing workflows. When you receive an event:
1. Parse the event payload to identify the event type
2. Log the event for audit purposes
3. Execute the appropriate workflow based on event type
4. Update local records if needed
## Event Types
Handle these Paddle event types:
- subscription_created: New subscription activated
- subscription_updated: Subscription modified
- subscription_cancelled: Subscription terminated
- subscription_payment_succeeded: Payment received
- subscription_payment_failed: Payment declined
- invoice_created: New invoice generated
## Processing Events
When processing an event:
1. Extract the subscription_id and customer_id
2. Look up the customer in your database
3. Execute business logic based on event type
4. Return a summary of actions taken
With the skill in place, Claude Code uses it as context when you ask billing-related questions, keeping generated code consistent with your workflow design.
Why Paddle + Claude Code?
Paddle handles the complexity of global tax compliance, invoice generation, and subscription management. When you pair it with Claude Code, you get AI assistance that understands your codebase and can generate boilerplate code, debug issues, and suggest improvements.
Claude Code excels at:
- Generating consistent webhook handler code
- Creating type-safe API clients
- Setting up test fixtures for payment scenarios
- Documenting billing flows
Prerequisites
Before starting, ensure you have:
- A Paddle account (sandbox mode for testing)
- Node.js 18+ or Python 3.9+
- Claude Code installed (
npm install -g @anthropic-ai/claude-code) - A project with an existing API structure
Step 1: Initialize Your Project with Claude Code
Start by asking Claude Code to help set up your billing module structure:
You: Help me set up a Paddle billing integration. I need webhook handlers, a subscription service, and API endpoints for managing subscriptions.
Claude Code will analyze your project structure and generate the appropriate files. For a Node.js/Express project, expect something like:
// src/billing/paddle-client.ts
import paddle from '@paddle/paddle-node-sdk';
export const paddleClient = new paddle.Client(
process.env.PADDLE_API_KEY,
{ environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox' }
);
Step 2: Configure Environment Variables
Never hardcode your Paddle API keys. Create a .env.example file and ask Claude Code to update your configuration:
You: Add Paddle environment configuration to my existing config module.
Your .env should include:
PADDLE_API_KEY=your_sandbox_api_key
PADDLE_WEBHOOK_SECRET=your_webhook_secret
PADDLE_VENDOR_ID=your_vendor_id
Step 3: Implement Webhook Handlers
Webhooks are critical for billing integrations. When a subscription is created, updated, or cancelled, Paddle sends events to your server. Claude Code can generate robust webhook handlers:
// src/billing/webhooks.ts
import { Request, Response } from 'express';
import { paddleClient } from './paddle-client';
import { prisma } from '../db/client';
export async function handlePaddleWebhook(req: Request, res: Response) {
const event = req.body;
// Verify webhook signature
const signature = req.headers['paddle-signature'] as string;
if (!paddleClient.verifyWebhookSignature(signature, req.body, process.env.PADDLE_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: 'Invalid signature' });
}
switch (event.event_type) {
case 'subscription.created':
await handleSubscriptionCreated(event);
break;
case 'subscription.updated':
await handleSubscriptionUpdated(event);
break;
case 'subscription.canceled':
await handleSubscriptionCanceled(event);
break;
case 'transaction.completed':
await handleTransactionCompleted(event);
break;
}
res.status(200).json({ received: true });
}
async function handleSubscriptionCreated(event: any) {
const { customer_id, subscription_id, items } = event.data;
await prisma.user.updateMany({
where: { paddleCustomerId: customer_id },
data: {
subscriptionId: subscription_id,
subscriptionStatus: 'active',
planId: items[0].price.product_id
}
});
}
If your backend uses Python, Claude Code can generate an equivalent signature verification function using the standard hmac library:
# process_subscription_event.py
import os
import hmac
import hashlib
def verify_webhook_signature(payload: str, signature: str) -> bool:
"""Verify that the webhook came from Paddle"""
secret = os.environ.get('PADDLE_WEBHOOK_SECRET')
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
The Python and TypeScript implementations follow the same HMAC-SHA256 pattern — always use compare_digest (or its equivalent) to prevent timing attacks.
Step 4: Create Subscription Management APIs
Claude Code can generate the RESTful endpoints for subscription management:
// src/api/subscriptions.ts
router.get('/subscriptions', requireAuth, async (req, res) => {
const user = req.user;
const subscriptions = await paddleClient.subscriptions.list({
customerId: user.paddleCustomerId
});
res.json(subscriptions);
});
router.post('/subscriptions', requireAuth, async (req, res) => {
const { priceId, paymentMethodId } = req.body;
const subscription = await paddleClient.subscriptions.create({
customerId: req.user.paddleCustomerId,
items: [{ priceId }],
paymentMethod: paymentMethodId,
currency: 'USD',
recurring: true
});
res.status(201).json(subscription);
});
router.post('/subscriptions/:id/cancel', requireAuth, async (req, res) => {
const { id } = req.params;
const { immediate } = req.body;
const result = await paddleClient.subscriptions.cancel(id, {
immediate: immediate ?? false
});
res.json(result);
});
Step 5: Handle Edge Cases
Billing systems have many edge cases. Ask Claude Code to help identify and handle them:
You: What edge cases should I handle for subscription billing? Add proper error handling and retry logic.
Key edge cases to consider:
- Failed payments: Implement automatic retries with exponential backoff
- Proration: Handle mid-cycle plan changes correctly
- Trial periods: Manage free trials and conversion
- Refunds: Process partial and full refunds
- Currency conversion: Handle multi-currency subscriptions
Here’s how Claude Code might suggest handling failed payments:
async function handlePaymentFailed(event: any) {
const { subscription_id, customer_id } = event.data;
const user = await prisma.user.findFirst({
where: { paddleCustomerId: customer_id }
});
if (!user) return;
// Send notification email
await emailService.sendPaymentFailed({
to: user.email,
subscriptionId: subscription_id
});
// Update status but don't cancel yet
await prisma.user.update({
where: { id: user.id },
data: { subscriptionStatus: 'past_due' }
});
}
For more nuanced retry scheduling, Claude Code can generate a strategy function that adjusts timing based on the reason for failure:
def get_retry_schedule(failure_reason: str) -> list:
"""Determine retry schedule based on failure type"""
if failure_reason == 'insufficient_balance':
# Gentle retry for temporary issues
return ['3 days', '7 days', '14 days']
elif failure_reason == 'card_expired':
# Urgent - card needs immediate update
return ['immediate']
else:
return ['1 day', '3 days', '7 days']
This failure-aware retry schedule prevents unnecessary retries on cards that clearly need updating while giving customers with temporary balance issues enough time to resolve them.
Invoice Lifecycle Handling
Invoices flow through several statuses (due → paid or overdue). Each status requires a different action. Ask Claude Code to generate a complete invoice event handler:
def process_invoice_webhook(event: dict) -> dict:
"""Process invoice events from Paddle"""
invoice = event['data']
invoice_id = invoice['id']
status = invoice['status']
if status == 'paid':
# Payment succeeded - fulfill the order
fulfill_order(invoice)
send_receipt(invoice)
return {'action': 'order_fulfilled'}
elif status == 'due':
# Invoice generated but not yet paid
send_payment_reminder(invoice)
return {'action': 'reminder_sent'}
elif status == 'overdue':
# Handle overdue invoice
handle_overdue_invoice(invoice)
return {'action': 'overdue_handled'}
return {'action': 'no_action_needed'}
Step 6: Testing Your Integration
Claude Code excels at generating test fixtures. Ask for help:
You: Create test fixtures for Paddle webhook events and write tests for the webhook handlers.
// tests/fixtures/paddle-events.ts
export const subscriptionCreatedEvent = {
event_type: 'subscription.created',
data: {
customer_id: 'cus_123',
subscription_id: 'sub_456',
items: [{ price: { product_id: 'pro_annual' } }],
status: 'active'
}
};
export const subscriptionCanceledEvent = {
event_type: 'subscription.canceled',
data: {
subscription_id: 'sub_456',
status: 'canceled'
}
};
Testing Webhooks Locally with ngrok and paddle-cli
Unit tests cover logic, but you also need to validate that Paddle can actually reach your local server. Use ngrok to expose your local port and paddle-cli to fire real sandbox events:
# Expose your local server to the internet
ngrok http 3000
# Configure Paddle sandbox to send webhooks to your ngrok URL, then trigger test events:
paddle-cli test-event subscription_created
paddle-cli test-event subscription_payment_failed
Make sure your webhook handler gracefully survives these edge cases during local testing:
- Duplicate webhook deliveries (Paddle may send the same event more than once)
- Webhook delivery during maintenance windows
- Partial refunds and credits
- Currency conversions for international customers
Use these fixtures to test your webhook handlers:
describe('Paddle Webhooks', () => {
it('should create subscription on subscription.created', async () => {
const req = {
body: subscriptionCreatedEvent,
headers: { 'paddle-signature': 'valid_signature' }
} as any;
await handlePaddleWebhook(req, mockRes);
expect(prisma.user.updateMany).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({ subscriptionStatus: 'active' })
})
);
});
});
Best Practices
- Always verify webhook signatures - Never trust incoming requests without validation
- Idempotency is key - Design handlers to handle duplicate events gracefully
- Log everything - Maintain audit trails for billing events
- Use webhooks for state, not API calls - Store event data and use it to update your database
- Test in sandbox - Always fully test in Paddle’s sandbox environment before production
Conclusion
Building a Paddle billing integration with Claude Code significantly accelerates development. The AI assistant helps generate type-safe code, identifies potential issues, and creates comprehensive tests. By following this guide and using Claude Code’s capabilities, you’ll have a production-ready billing system that handles subscriptions, webhooks, and payment failures elegantly.
Remember to thoroughly test your integration in Paddle’s sandbox environment before deploying to production. With proper error handling and webhook processing, your billing system will be robust and reliable.
Related Reading
- Claude Code for Beginners: Complete Getting Started Guide
- Best Claude Skills for Developers in 2026
- Claude Skills Guides Hub
Built by theluckystrike — More at zovo.one