Complete reference for Stripe webhook events processed by iaptic. All webhooks require Stripe API version 2024-11-20.acacia
.
Event Types
Subscription Lifecycle
checkout.session.completed
Sent when a checkout session is completed successfully.
{
type: 'checkout.session.completed',
data: {
object: {
id: string; // cs_xyz
subscription: string; // sub_xyz
customer: string; // cus_xyz
metadata: {
application_username: string;
managed_by_iaptic: 'true';
}
}
}
}
customer.subscription.created
Sent when a new subscription is created.
{
type: 'customer.subscription.created',
data: {
object: {
id: string; // sub_xyz
status: 'active' | 'trialing';
current_period_end: number;
items: {
data: [{
price: { id: string } // price_xyz
}]
},
metadata: {
application_username: string;
managed_by_iaptic: 'true';
}
}
}
}
customer.subscription.updated
Sent when a subscription is updated (plan change, renewal, etc).
{
type: 'customer.subscription.updated',
data: {
object: {
id: string;
status: 'active' | 'past_due' | 'canceled';
cancel_at_period_end: boolean;
current_period_end: number;
items: {
data: [{
price: { id: string }
}]
}
},
previous_attributes?: {
items?: {
data: [{
price: { id: string }
}]
},
metadata?: {
application_username?: string;
}
}
}
}
Payment Events
invoice.paid
Sent when an invoice is paid successfully.
{
type: 'invoice.paid',
data: {
object: {
id: string; // in_xyz
subscription: string; // sub_xyz
paid: true;
amount_paid: number;
currency: string;
billing_reason: string;
}
}
}
invoice.payment_failed
Sent when an invoice payment fails.
{
type: 'invoice.payment_failed',
data: {
object: {
id: string;
subscription: string;
paid: false;
attempt_count: number;
next_payment_attempt?: number;
}
}
}
payment_intent.succeeded
Sent when a payment is successfully processed.
{
type: 'payment_intent.succeeded',
data: {
object: {
id: string; // pi_xyz
amount: number;
currency: string;
metadata: {
managed_by_iaptic: 'true';
}
}
}
}
Webhook Processing
Verification
All webhooks must be verified using the signing secret:
const event = stripe.webhooks.constructEvent(
rawBody, // Raw request body
signature, // Stripe-Signature header
webhookSecret // Your webhook secret
);
Required Headers
Stripe-Signature
: Webhook signatureContent-Type
:application/json
Stripe-Version
:2024-11-20.acacia
Response Codes
200
: Event processed successfully401
: Invalid signature400
: Invalid payload500
: Processing error
Event Processing Order
Events should be processed in chronological order:
For new purchases:
customer.subscription.created
payment_intent.succeeded
(if the product is a one-time payment)
For subscription updates:
customer.subscription.updated
Metadata Requirements
All managed subscriptions must have:
metadata: {
managed_by_iaptic: 'true',
application_username: string // User identifier
}
Error Handling
Common Issues
-
Invalid Signature
try { const event = stripe.webhooks.constructEvent(/*...*/); } catch (err) { if (err.type === 'StripeSignatureVerificationError') { return res.status(401).json({ error: 'Invalid signature' }); } }
-
Missing Metadata
if (!event.data.object.metadata?.managed_by_iaptic) { return res.status(400).json({ error: 'Not managed by iaptic' }); }
-
Wrong API Version
if (event.api_version !== '2024-11-20.acacia') { return res.status(400).json({ error: 'Invalid API version' }); }
Testing Webhooks
Using Stripe CLI:
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Listen to webhooks
stripe listen --forward-to localhost:3000/webhook
# Trigger specific events
stripe trigger customer.subscription.created
stripe trigger customer.subscription.updated
stripe trigger invoice.payment_failed