5 min read

Set Up Webhooks

This guide explains how to configure Stripe webhooks to keep your application in sync with subscription changes.

Configure Webhook Endpoint

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Click "Add endpoint"
  3. Select API version: 2024-11-20.acacia
  4. Enter the iaptic webhook URL:
    https://validator.iaptic.com/v3/webhook/stripe
    

Required Events

Select these webhook events:

[
  "customer.subscription.created",
  "customer.subscription.updated",
  "customer.subscription.deleted",
  "payment_intent.succeeded",
  "payment_intent.payment_failed",
  "price.created",
  "price.updated",
  "price.deleted",
  "product.created",
  "product.updated",
  "product.deleted"
]

Configure iaptic

  1. Copy the webhook signing secret (whsec_...)
  2. Go to iaptic Dashboard → Settings
  3. Enter the webhook secret:
    Stripe Webhook Secret: whsec_...
    

Test the Configuration

  1. Use Stripe's webhook tester:

    stripe trigger customer.subscription.updated
    
  2. Check iaptic logs for the event:

    // In your application
    const { purchases } = await iaptic.getPurchases('sub_xyz');
    console.log('Updated subscription:', purchases[0]);
    

Webhook Events Reference

Subscription Lifecycle

// checkout.session.completed
// New subscription created
{
  type: 'checkout.session.completed',
  data: {
    object: {
      subscription: 'sub_xyz',
      metadata: {
        application_username: 'user123'
      }
    }
  }
}

// customer.subscription.updated
// Plan changed, renewal, cancellation
{
  type: 'customer.subscription.updated',
  data: {
    object: {
      id: 'sub_xyz',
      status: 'active',
      cancel_at_period_end: false
    },
    previous_attributes: {
      items: [{ price: { id: 'price_old' } }]
    }
  }
}

// customer.subscription.deleted
// Subscription ended
{
  type: 'customer.subscription.deleted',
  data: {
    object: {
      id: 'sub_xyz',
      status: 'canceled'
    }
  }
}

Best Practices

  1. Event Processing

    • Handle events idempotently
    • Process in chronological order
    • Verify webhook signatures
    • Log all events
  2. Error Handling

    • Retry failed webhooks
    • Monitor webhook health
    • Set up alerts for failures
    • Keep audit logs
  3. Security

    • Keep signing secret secure
    • Validate all webhooks
    • Monitor for suspicious activity
    • Rotate secrets periodically

Common Issues

Events Not Received

  1. Check webhook configuration
  2. Verify signing secret
  3. Check event selection
  4. Test with Stripe CLI

Invalid Signature

  1. Verify secret matches
  2. Check for request modification
  3. Ensure raw body is preserved
  4. Verify timestamp tolerance

Out of Order Events

  1. Use event timestamps
  2. Implement idempotency
  3. Handle race conditions
  4. Log event sequence

Example: Event Handler

class WebhookHandler {
  constructor(stripeSecret) {
    this.stripeSecret = stripeSecret;
  }

  async handleWebhook(rawBody, signature) {
    try {
      // 1. Verify signature
      const event = stripe.webhooks.constructEvent(
        rawBody,
        signature,
        this.stripeSecret
      );

      // 2. Process based on type
      switch (event.type) {
        case 'customer.subscription.updated':
          await this.handleSubscriptionUpdate(event.data.object);
          break;
        case 'invoice.payment_failed':
          await this.handlePaymentFailure(event.data.object);
          break;
        // ... handle other events
      }

      // 3. Log success
      await this.logWebhookEvent(event);

      return { ok: true };
    } catch (error) {
      // 4. Log failure
      await this.logWebhookError(error);
      throw error;
    }
  }

  async handleSubscriptionUpdate(subscription) {
    // Implement your update logic
  }

  async handlePaymentFailure(invoice) {
    // Implement your failure handling
  }

  async logWebhookEvent(event) {
    // Implement your logging
  }

  async logWebhookError(error) {
    // Implement your error logging
  }
}