Basic Understanding of Webhooks with examples
Tags: ruby, rails, webhooks, beginners
Published: 2023-03-19 06:40:31

Webhooks are a way for an external service to notify your application about events that occur in the service. In other words, it's a way for your application to receive real-time notifications of events happening outside of your application.

In the context of Ruby on Rails, webhooks are commonly used to integrate with third-party services such as Stripe, PayPal, and others. These services use webhooks to send notifications to your application about events such as successful payments, failed payments, new subscribers, and other events.

By using webhooks in your Ruby on Rails application, you can automate processes that would otherwise require manual intervention, such as updating a user's account status or sending notifications to users. This can improve the user experience and streamline your application's workflow.

Webhooks can be implemented using a variety of protocols such as HTTP, HTTPS, and others. In Ruby on Rails, webhooks are typically implemented using controller actions that receive and process webhook requests, and background jobs to perform the actual work of handling the webhook events.

Overall, webhooks provide a powerful way to integrate your Ruby on Rails application with third-party services and automate processes in your application.

Handling webhooks efficiently and with proper error handling in a Ruby on Rails application is important to ensure that your application can handle large volumes of webhook requests and recover from any errors that may occur. In this example, we'll use the popular payment processing service Stripe to demonstrate how to handle webhooks efficiently and with proper error handling in a Ruby on Rails application.

**Setup**
The first step is to install the Stripe gem and configure it in your Rails application. You can do this by adding the following code to your Gemfile:
`gem 'stripe'`

Then, run bundle install to install the gem and generate a `config/initializers/stripe.rb` file to configure the Stripe API keys:

```ruby
Rails.configuration.stripe = {
  publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
  secret_key: ENV['STRIPE_SECRET_KEY']
}
Stripe.api_key = Rails.configuration.stripe[:secret_key]
```

**Create a Webhooks Controller**
Next, you'll need to create a controller to handle webhook requests. You can create a new `WebhooksController` with the following command:
`rails generate controller webhooks`

In the `webhooks_controller.rb` file, define a new method to handle webhook requests:

```ruby
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def receive
    event = Stripe::Event.construct_from(params.to_unsafe_h)

    # Handle the event
    begin
      case event.type
      when 'payment_intent.succeeded'
        handle_payment_succeeded(event.data.object)
      when 'payment_intent.payment_failed'
        handle_payment_failed(event.data.object)
      else
        raise "Unhandled event type: #{event.type}"
      end
    rescue StandardError => e
      render json: { error: e.message }, status: :unprocessable_entity
    end

    render json: { message: 'Webhook received successfully' }, status: :ok
  end

  private

    def handle_payment_succeeded(payment_intent)
        # Find the user associated with the payment
        user = User.find_by(stripe_customer_id: payment_intent.customer)

        # Check if the payment covers any outstanding invoices
        invoices = user.invoices.unpaid
        invoices.each do |invoice|
            if invoice.amount_due <= payment_intent.amount_received
            invoice.update(status: 'paid', paid_at: Time.now)
            payment_intent.amount_received -= invoice.amount_due
            end
        end

        # Create a new payment record
        payment = Payment.create(
            user: user,
            amount: payment_intent.amount_received,
            payment_method: payment_intent.payment_method,
            payment_intent_id: payment_intent.id,
            status: payment_intent.status,
            paid_at: payment_intent.created
        )

        # Send a confirmation email to the user
        UserMailer.payment_received(user, payment).deliver_later
        rescue => e
         Rails.logger.error("Error handling payment failed webhook: #{e}")
    end

    def handle_payment_failed(payment_intent)
        # Find the user associated with the payment
        user = User.find_by(stripe_customer_id: payment_intent.customer)

        # Handle any unpaid invoices
        invoices = user.invoices.unpaid
        invoices.each do |invoice|
            invoice.update(status: 'failed', failed_at: Time.now)
        end

        # Create a new payment record
        payment = Payment.create(
            user: user,
            amount: payment_intent.amount_received,
            payment_method: payment_intent.payment_method,
            payment_intent_id: payment_intent.id,
            status: payment_intent.status,
            failed_at: payment_intent.created
        )

        # Send a notification email to the user
        UserMailer.payment_failed(user, payment).deliver_later
        rescue => e
         Rails.logger.error("Error handling payment failed webhook: #{e}")
    end
end
```
we first find the user associated with the payment by looking up their Stripe customer ID. We then check if the payment covers any outstanding invoices by iterating over the user's unpaid invoices and updating their status to "paid" if the payment amount is sufficient.

We then create a new Payment record in our database with information from the payment_intent object, including the user, payment amount, payment method, payment intent ID, and payment status. We also set the paid_at timestamp to the time the payment was created.


Then we find the user associated with the payment by looking up their Stripe customer ID. We then handle any unpaid invoices by iterating over the user's unpaid invoices and updating their status to "failed" with the update method.

We then create a new Payment record in our database with information from the payment_intent object, including the user, payment amount, payment method, payment intent ID, and payment status. We also set the failed_at timestamp to the time the payment was created.

Finally, we send a notification email to the user using a UserMailer object and the deliver_later method to send the email asynchronously.

**Configure Webhooks in Stripe**
Now that your application is set up to handle webhook requests, you need to configure your Stripe account to send webhook requests to your application. Log in to your Stripe dashboard and navigate to the "Webhooks" tab.

Click the "Add Endpoint" button and enter the URL for your webhook controller action (e.g. https://example.com/webhooks/receive). You can leave the other fields at their default values.

Finally, select the events you want to receive and click the "Add Endpoint" button. Stripe will now send webhook requests to your application whenever the selected events occur.

**Testing**
To test your webhook handling, you can use the Stripe CLI to simulate events. Install the Stripe CLI and run the following command: `stripe trigger payment_intent.succeeded`

This will simulate a payment_intent.succeeded event and send a webhook request to your application. Check your application logs to confirm that the webhook was received and handled successfully.

You can also simulate a failed payment by running:
`stripe trigger payment_intent.payment_failed`

This will simulate a payment_intent.payment_failed event and send a webhook request to your application. Again, check your application logs to confirm that the webhook was received and handled successfully.

If any errors occur during webhook handling, check your logs for error messages and make sure that your error handling code is working correctly.

It's also a good idea to test your webhook handling with a variety of scenarios, including successful and failed payments, network errors, and unexpected events.

And that's it! You now have a robust and efficient webhook handling system in your Ruby on Rails application. With proper error handling and testing, your application can handle large volumes of webhook requests and recover from any errors that may occur.