★ Sending and receiving webhooks in Laravel apps
Tags: laravel, php, webhooks
Published: 2020-01-02 23:30:04

---
title: ★ Sending and receiving webhooks in Laravel apps
published: true
date: 2019-06-17 12:34:02 UTC
tags: laravel, php, webhooks
canonical_url: https://freek.dev/1383-sending-and-receiving-webhooks-in-laravel-apps
---

A webhook is a mechanism where an application can notify an other application that something has happened. Technically, the application sends an HTTP request to that other application. In this blog post, I'd like to introduce you to two packages that we recently released. The first is [laravel-webhook-server](https://github.com/spatie/laravel-webhook-server), which allows you to send webhook requests. The second one is [laravel-webhook-client](https://github.com/spatie/laravel-webhook-client), which makes it easy to receive those webhook request.

Ready to dig in? Let's go!

## When are webhooks useful? #

Webhooks are very useful when a system wants to be notified as soon as something happens in another system. Let's make that a bit less abstract and take a look at a real-world example.

[Oh Dear](https://ohdear.app) is an uptime tracker that I've built together with my buddy [Mattias](https://twitter.com/mattiasgenia). The users of our app want us to let them know as soon as one of their sites is down. Of course, we can send our users notification, via e-mail, Slack, SMS and so on.

But what if one of our users wants to know in a system of their own that a site is down? We don't want them to poll us via [our API](https://ohdear.app/docs/api/introduction) every second to check. Instead, we offer webhooks. We'll send an HTTP requests [on various events](https://ohdear.app/docs/webhooks/introduction). At [Spatie](https://spatie.be), we use these webhooks, to [display the sites that are down on our dashboard](https://freek.dev/960-displaying-sites-that-are-down-on-a-dashboard-using-oh-dear-webhooks).

A lot of services offer webhooks: Slack, [Stripe](https://stripe.com/docs/webhooks) (you can handle those using [this package](https://freek.dev/870-handling-stripe-webhooks-in-a-laravel-application)), [GitHub](https://developer.github.com/webhooks/), ... If you're building a system where something happens that another app is interested in, consider adding support for webhooks.

## Sending webhooks in Laravel apps #

After having coded up support for webhooks in several projects, and after having seen [Lorna Jane](https://lornajane.net/)'s [excellent talk](https://noti.st/lornajane/IxSlX9#sv5yAxi) on this subject at [Laravel Live UK 2019](https://laravellive.uk/), I thought it was time to finally code up a package to send webhooks.

[laravel-webhook-server](https://github.com/spatie/laravel-webhook-server) allows you to configure and send webhooks in a Laravel app easily. So you'll install this into the app that wants to notify other apps that something has happened. The package has support for signing calls, retrying calls, and backoff strategies. Before going further into that, let's first take a look at how you can send out webhook calls.

```php
WebhookCall::create()
   ->url('https://other-app.com/webhooks')
   ->payload(['key' => 'value'])
   ->useSecret('sign-using-this-secret')
   ->dispatch();
```

This will send a POST request to `https://other-app.com/webhooks`. The body of the request will be JSON encoded version of the array passed to `payload`.

### Signing webhook requests #

When setting up webhooks, it's common to generate, store, and share a secret between your app and the app that wants to receive webhooks. Generating and storing the secret itself is out of scope for the package but could be done easily with `Illuminate\Support\Str::random()`.

Our package will add a header called `Signature` to every webhook request it makes. That header contains a signature the receiving app can use the payload hasn't been tampered with. The value of the `Signature` is computed by the package like this:

```php
$payloadJson = json_encode($payload); 

// that secret is the string passed to `useSecret`
$signature = hash_hmac('sha256', $payloadJson, $secret);
```

Signing a header makes it impossible for outsiders, who don't have access to the shared secret, to compute the right value of the signature.

Of course, you can customize the signing process by creating [your own signer](https://github.com/spatie/laravel-webhook-server#customizing-signing-requests). A signer is any class that implements this simple interface.

```php
namespace Spatie\WebhookServer\Signer;

interface Signer
{
    public function signatureHeaderName(): string;

    public function calculateSignature(array $payload, string $secret): string;
}
```

Though the package doesn't force you to, I'd recommend only calling other apps via HTTPS, so others can't even see the data transmitted.

### Using a backoff strategy #

Let's take a look at the code to send a webhook again.

```php
WebhookCall::create()
   ->url('https://other-app.com/webhooks')
   ->payload(['key' => 'value'])
   ->useSecret('sign-using-this-secret')
   ->dispatch();
```

You might wonder why the method to kick off the webhook sending process is called `dispatch`, and not something like `send`. Well, behind the scenes, a job to call the webhook is dispatched.

If the receiving app doesn't respond with a response code starting with `2` (or it doesn't respond within 3 seconds), the package will release the job back into the queue with a delay. We add a delay because we don't want to hammer on the receiving app. Maybe it has some load or other issues right now, and hopefully it will be healthy again a bit later.

The time there should be between attempts is determined by a backoff strategy. Here's the default one:

```php
namespace Spatie\WebhookServer\BackoffStrategy;

class ExponentialBackoffStrategy implements BackoffStrategy
{
    public function waitInSecondsAfterAttempt(int $attempt): int
    {
        if ($attempt > 4) {
            return 100000;
        }

        return 10 ** $attempt;
    }
}
```

So with each attempt, we'll have more time between attempts, with a maximum of 100000 seconds (that's roughly 27 hours). Of course, you can [customize that backoff behavior](https://github.com/spatie/laravel-webhook-server#retrying-failed-webhooks).

By default, the package will try to send the webhook request only three times. Of course, you customize that number in the config file. If the request failed after that last attempt, the package will fire the `FinalWebhookCallFailedEvent`. You could use that event to, for example, send a mail to the owner of the app to notify that a webhook couldn't be processed. If a lot of those events are fired for the same URL, you could determine it's time to turn off the webhooks temporarily for that URL. That's all up to you.

## Receiving webhooks in Laravel apps #

After having created a package to send webhooks in Laravel, it only seemed logical to also create a package to receive them. [laravel-webhook-client](https://github.com/spatie/laravel-webhook-client) has support for verifying signed calls, storing payloads, and processing the payloads in a queued job.

Installing the package encompasses adding a route like this.

```php
Route::webHooks('webhook-receiving-url');
```

For every request that hits that endpoint a couple of steps will be performed. Let's review them.

### Verifying the signature #

First, we'll verify if the signature of the request is valid. The verification is basically the inverse of the signing done by [laravel-webhook-server](https://github.com/spatie/laravel-webhook-server). The package will take a look at the `Signature` header and compare it's contents to a computed version of the signture that's being calculated like this:

```php
$computedSignature = hash_hmac('sha256', $request->getContent(), $configuredSigningSecret);
```

If the `$computedSignature` does not match the content of the `Signature header` the package will respond with a 500` and discard the request. Of course, you can [customize the signature verification process](https://github.com/spatie/laravel-webhook-client#creating-your-own-signature-validator).

### Filtering out unwanted requests #

It could be that the app sending out the webhooks, sends out webhook requests for events you're not interested in. Using a webhook profile, you can filter out these uninteresting calls.

All requests that have a valid signature will be passed to a webhook profile. By default, the package will use the profile called `ProcessEverythingWebhookProfile` which will determine that all requests are of interest.

Creating a custom webhook profile is easy. Just implement this interface:

```php
namespace Spatie\WebhookClient\WebhookProfile;

use Illuminate\Http\Request;

interface WebhookProfile
{
    public function shouldProcess(Request $request): bool;
}
```

### Storing and processing the webhook request #

If the webhook profile determines that request is of interest, we'll first store the request in the `webhook_calls` table. After that, we'll pass that newly created `WebhookCall` model to a queued job.

Most webhook sending apps expect you to respond very quickly. Offloading the real processing work to a queued job allows for speedy responses. You can specify which job should process the webhook in the `process_webhook_job` in the config file of the package. A valid job is any class that extends `\Spatie\WebhookClient\ProcessWebhookJob`. Here's an example.

```php
namespace App\Jobs;

use \Spatie\WebhookClient\ProcessWebhookJob as SpatieProcessWebhookJob;

class YourProcessWebhookJob extends SpatieProcessWebhookJob
{
    public function handle()
    {
        // $this->webhookCall // contains an instance of `WebhookCall`
    
        // perform the work here
    }
}
```

Should an exception be thrown while queueing the job, the package will store that exception in the `exception` attribute on the `WebhookCall` model.

After the job has been dispatched, the controller will respond with a `200` status code. The webhook sending app now knows that the webhook request was well received.

If anything goes wrong during the execution of the job, you still have the webhook request available in the `webhook_calls` table. After diagnosing and fixing the problem, you should be able to restart the webhook processing job.

## In closing #

If you need to send and/or receive webhooks in Laravel take a look at [laravel-webhook-server](https://github.com/spatie/laravel-webhook-server) and/or [laravel-webhook-client](https://github.com/spatie/laravel-webhook-client) packages. They are feature rich and robust. In the blog post above I've highlighted the most important bits, but the readmes of the package contain much more info.

These aren't the first packages to come out of team [Spatie](https://spatie.be). Do take a look at the [list of packages](https://spatie.be/open-source/packages) we've made previously. If you use any of our code, don't forget to [send us a postcard](https://spatie.be/open-source/postcards).