Integrating Chargebee Webhooks with Laravel

Integrating Chargebee Webhooks with Laravel

Deniz Birlik
Deniz Birlik
·10 min read

As a developer who has worked extensively with subscription management systems, I'm excited to guide you through the process of integrating Chargebee webhooks with your Laravel application. This integration is crucial for maintaining real-time synchronization between your application and Chargebee, ensuring that your business logic responds promptly to subscription events.

Why Chargebee Webhooks?

Before we dive into the implementation, let's discuss the importance of Chargebee webhooks.

Imagine you're running a SaaS platform. A customer upgrades their subscription plan, and you need to immediately update their access level, send a confirmation email, and update your financial records. While you could poll the Chargebee API periodically, webhooks provide a more efficient, real-time solution.

Chargebee webhooks allow your application to receive instant notifications about various events - subscription creations, changes, cancellations, payment successes or failures, and more. This real-time communication ensures your application remains in perfect sync with your subscription data in Chargebee.

Setting Up Your Laravel Project

First, let's set up our Laravel project. If you haven't already, create a new Laravel project:

composer create-project --prefer-dist laravel/laravel chargebee-webhook-demo
cd chargebee-webhook-demo

Configuring Chargebee in Laravel

Let's set up our Chargebee configuration. Add this to your config/services.php file:

<?php

    return [
        // Other services...
        "chargebee" => [
            "allowed_ips" => [
                "3.209.65.25",
                "3.215.11.205",
                "3.228.118.137",
                "3.229.12.56",
                "3.230.149.90",
                "3.231.198.174",
                "3.231.48.173",
                "3.233.249.52",
                "18.210.240.138",
                "18.232.249.243",
                "34.195.242.184",
                "34.206.183.55",
                "35.168.199.245",
                "52.203.173.152",
                "52.205.125.34",
                "52.45.227.101",
                "54.158.181.196",
                "54.163.233.122",
                "54.166.107.32",
                "54.84.202.184",
            ],
        ],
    ];

Creating the Webhook Controller

Now, let's create a controller to handle our webhook requests:

php artisan make:controller ChargebeeWebhookController

Open the newly created app/Http/Controllers/ChargebeeWebhookController.php and let's add some code:

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use IlluminateSupportFacadesLog;

class ChargebeeWebhookController extends Controller
{
    public function handleWebhook(Request $request)
    {
        // Verify the webhook IP
        if (!$this->verifyWebhookIP($request)) {
            return response('Unauthorized IP', 403);
        }

        $payload = $request->all();

        // Process the webhook
        $eventType = $payload['event_type'];

        switch ($eventType) {
            case 'payment_intent_created':
                $this->handlePaymentIntentCreated($payload['content']['payment_intent']);
                break;
            case 'subscription_created':
                $this->handleSubscriptionCreated($payload['content']['subscription']);
                break;
            // Add more cases as needed
            default:
                Log::info('Unhandled webhook event: ' . $eventType);
                break;
        }

        return response('Webhook processed', 200);
    }

    protected function verifyWebhookIP(Request $request)
    {
        $allowedIPs = config('services.chargebee.allowed_ips');
        $requestIP = $request->ip();

        return in_array($requestIP, $allowedIPs);
    }

    protected function handlePaymentIntentCreated($paymentIntent)
    {
        // Logic to handle a new payment intent
        Log::info('New payment intent created: ' . $paymentIntent['id']);
        // Update your database, send notifications, etc.
    }

    protected function handleSubscriptionCreated($subscription)
    {
        // Logic to handle a new subscription
        Log::info('New subscription created: ' . $subscription['id']);
        // Update your database, send notifications, etc.
    }
}

This controller verifies the webhook IP and processes different types of events.

Setting Up the Route

Now, let's set up a route for our webhook. In routes/api.php, add:

use AppHttpControllersChargebeeWebhookController;

Route::post('/chargebee/webhook', [ChargebeeWebhookController::class, 'handleWebhook']);

Handling Specific Events

With the foundational setup complete, let's focus on handling specific webhook events. We'll expand our handleSubscriptionCreated method to demonstrate how to process subscription notifications effectively:

protected function handleSubscriptionCreated($subscription)
{
    Log::info('New subscription created: ' . $subscription['id']);

    // Create or update user
    $user = User::updateOrCreate(
        ['chargebee_customer_id' => $subscription['customer_id']],
        [
            'plan' => $subscription['plan_id'],
            'subscription_status' => $subscription['status'],
        ]
    );

    // Update subscription details
    Subscription::updateOrCreate(
        ['chargebee_subscription_id' => $subscription['id']],
        [
            'user_id' => $user->id,
            'plan_id' => $subscription['plan_id'],
            'status' => $subscription['status'],
            'next_billing_at' => $subscription['current_term_end'],
        ]
    );

    // Send welcome email
    Mail::to($user->email)->send(new WelcomeSubscriber($user));

    // Provision resources based on the plan
    $this->provisionResources($user, $subscription['plan_id']);
}

This method updates the user and subscription details in your database, sends a welcome email, and provisions resources based on the subscribed plan.

Testing Your Webhook

Testing webhooks can be tricky, especially when you're developing locally. You need a way to receive webhooks on your local machine and a method to simulate different webhook events. This is where Webhook Simulator comes in handy.

Webhook Simulator is a powerful tool designed specifically for testing webhook integrations. It allows you to simulate webhook events from various services, including Chargebee, without the need to set up actual subscription flows or expose your local environment to the internet.

Here's how you can use Webhook Simulator to test your Chargebee webhook integration:

  1. Sign up for a lifetime membership at Webhook Simulator. Our affordable one-time payment gives you unlimited access to all current and future features, making it an excellent investment for developers working on webhook integrations. Once signed up, visit the "Platforms" page and add OpenPhone to your library.

  2. Install the Webhook Simulator CLI. You can find detailed installation instructions in the documentation.

  3. Once you have the CLI installed, use the following command to start listening for webhook events and forward them to your local Laravel application:

ws-cli listen --forward-to http://localhost:8000/api/chargebee/webhook

Make sure to replace http://localhost:8000/api/chargebee/webhook with the actual URL where your Laravel application is listening for webhooks.

  1. Now, you have two options to simulate Chargebee webhook events:

a. Use the Webhook Simulator web application to create and send custom payloads.

b. Use the CLI to trigger predefined events. Open a new terminal window and run:

ws-cli trigger chargebee payment_intent.created

Replace payment_intent.created with any other Chargebee event you want to test.

  1. The simulated webhook will be sent to your local Laravel application via the forwarding set up in step 3.

  2. Check your Laravel logs to see how your application handled the simulated webhook event.

Here's an example of what a Chargebee payment_intent.created event payload might look like (note that this is a simplified version):

{
  "content": {
    "payment_intent": {
      "amount": 5004,
      "currency_code": "USD",
      "gateway": "chargebee",
      "id": "XpbT68aRfcRkml9lrcdYNND663i3E0a1w0ZhtcMow1K07oDEy",
      "status": "inited",
      ...
    }
  },
  "event_type": "payment_intent.created",
  "id": "ev_XpbT68aRfcRkmt9q",
  "object": "event",
  "occurred_at": 1368350326
}

Using Webhook Simulator, you can easily test different scenarios, including edge cases and error conditions, without having to process actual subscriptions or wait for specific events to occur in Chargebee.

Remember, while Webhook Simulator is great for development and testing, you should still test with real Chargebee webhooks in a staging environment before going to production. Chargebee provides a sandbox environment for this purpose.

Error Handling and Logging

Robust error handling and logging are crucial when working with webhooks. Let's update our controller to include better error handling:

public function handleWebhook(Request $request)
{
    try {
        if (!$this->verifyWebhookIP($request)) {
            Log::warning('Unauthorized webhook IP: ' . $request->ip());
            return response('Unauthorized IP', 403);
        }

        $payload = $request->all();
        $eventType = $payload['event_type'];

        switch ($eventType) {
            case 'payment_intent_created':
                $this->handlePaymentIntentCreated($payload['content']['payment_intent']);
                break;
            case 'subscription_created':
                $this->handleSubscriptionCreated($payload['content']['subscription']);
                break;
            default:
                Log::info('Unhandled webhook event: ' . $eventType);
                break;
        }

        return response('Webhook processed', 200);
    } catch (Exception $e) {
        Log::error('Error processing webhook: ' . $e->getMessage());
        return response('Error processing webhook', 500);
    }
}

This ensures that any exceptions are caught and logged, preventing Chargebee from receiving an error response (which could cause them to retry the webhook unnecessarily).

Queuing Webhook Processing

For time-consuming tasks, it's advisable to queue the webhook processing to prevent timeouts. Create a job:

php artisan make:job ProcessChargebeeWebhook

Then, update the job file (app/Jobs/ProcessChargebeeWebhook.php):

<?php

namespace AppJobs;

use IlluminateBusQueueable;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationBusDispatchable;
use IlluminateQueueInteractsWithQueue;
use IlluminateQueueSerializesModels;

class ProcessChargebeeWebhook implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $payload;

    public function __construct($payload)
    {
        $this->payload = $payload;
    }

    public function handle()
    {
        $eventType = $this->payload['event_type'];

        switch ($eventType) {
            case 'payment_intent_created':
                // Handle payment intent created
                break;
            case 'subscription_created':
                // Handle subscription created
                break;
            // Add more cases as needed
        }
    }
}

Now, update your controller to dispatch this job instead of processing the webhook directly:

public function handleWebhook(Request $request)
{
    if (!$this->verifyWebhookIP($request)) {
        return response('Unauthorized IP', 403);
    }

    ProcessChargebeeWebhook::dispatch($request->all());

    return response('Webhook queued for processing', 202);
}

This approach allows your application to quickly acknowledge receipt of the webhook while processing it in the background.

Wrapping Up

Integrating Chargebee webhooks with your Laravel application opens up a world of possibilities for real-time subscription management. We've covered the basics of setting up webhook handling, verifying IPs, processing events, and even touched on more advanced topics like queuing.

Remember, when working with subscription systems and payment data, always prioritize security and thorough testing. Chargebee provides excellent documentation and tools to help you along the way.

Happy coding, and may your subscriptions always renew smoothly!