Stars: 334
Forks: 18
Pull Requests: 11
Issues: 10
Watchers: 4
Last Updated: 2023-04-16 07:39:54
Receiver is a drop-in webhook handling library for Laravel.
License: MIT License
Languages: PHP
Receiver is a drop-in webhook handling library for Laravel.
Webhooks are a powerful part of any API lifecycle. Receiver aims to make handling incoming webhooks in your Laravel app a consistent and easy process.
Out of the box, Receiver has built in support for:
Of course, Receiver can receive webhooks from any source using custom providers.
Requires:
composer require hotmeteor/receiver
Optional:
Stripe support requires stripe/stripe-php
Webhooks require an exposed endpoint to POST to. Receiver aims to make this a one-time setup that supports any of your incoming webhooks.
<?php
namespace App\Http\Controllers\Webhooks;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class WebhooksController extends Controller
{
public function store(Request $request)
{
return Receiver::driver('slack')
->receive($request)
->ok();
}
}
The methods being used are simple:
driver
that should process the webhookreceive
the request for handling200
ok
responseMaybe you have webhooks coming in from multiple services -- handle them all from one controller with a driver variable from your route.
<?php
namespace App\Http\Controllers\Webhooks;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class WebhooksController extends Controller
{
public function store(Request $request, string $driver)
{
return Receiver::driver($driver)
->receive($request)
->ok();
}
}
The provided ReceivesWebhooks
trait will take care of this for you.
<?php
namespace App\Http\Controllers\Webhooks;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Receiver\ReceivesWebhooks;
class WebhooksController extends Controller
{
use ReceivesWebhooks;
}
Note: you'll still need to create the route to this action. Example:
Route::post('/hooks/{driver}', [\App\Http\Controllers\Webhooks\WebhooksController::class, 'store'])
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);
Receiver allows you to safely handle webhooks for events you do not handle. Add a fallback
method before ok
– it takes a callback that is passed the webhook object.
<?php
namespace App\Http\Controllers\Webhooks;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Receiver\Providers\Webhook;
class WebhooksController extends Controller
{
public function store(Request $request, string $driver)
{
return Receiver::driver($driver)
->receive($request)
->fallback(function(Webhook $webhook) {
// Do whatever you like here...
})
->ok();
}
}
Now that webhooks are being received they need to be handled. Receiver will look for designated Handler
classes for each event type that comes in in the App\Http\Handlers\[Driver]
namespace. Receiver does not provide these handlers -- they are up to you to provide as needed. If Receiver doesn't find a matching handler it simplies ignores the event and responds with a 200 status code.
For example, a Stripe webhook handler would be App\Http\Handlers\Stripe\CustomerCreated
for the incoming customer.created
event.
Each handler is constructed with the event
(name of the webhook event) and data
properties.
Each handler must also use the Dispatchable
trait.
<?php
namespace App\Http\Handlers\Stripe;
use Illuminate\Foundation\Bus\Dispatchable;
class CustomerCreated
{
use Dispatchable;
public function __construct(public string $event, public array $data)
{
}
public function handle()
{
// Your code here
}
}
Of course, since your app may be receiving a lot of webhooks it might be better practice to queue these handlers. That way your app can efficiently respond back to the service that the webhook was received and requests aren't being blocked as events are handled.
Receiver attempts to dispatch
every handled event, so queueing handlers is simply a matter of setting them up like any Laravel queued job:
<?php
namespace App\Http\Handlers\Stripe;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CustomerCreated implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public function __construct(public string $event, public array $data)
{
}
public function handle()
{
// Your code here
}
}
As mentioned previously, Receiver can handle webhooks from any source. Even though there are a few providers distributed with the package, Receiver can easily be extended to work with other apps.
The easiest way to add a new provider is to use the included Artisan command:
php artisan receiver:make <name>
This command will generate a new provider with the name you defined. This class will be created in the App\Http\Receivers
namespace.
If your provider needs to be able to verify webhook signatures simply add the --verified
flag to the command:
php artisan receiver:make <name> --verified
Once you've created your new provider you can simply extend Receiver in your AppServiceProvider
so that Receiver can use it:
<?php
namespace App\Providers;
use App\Http\Receivers\MailchimpProvider;
use App\Http\Receivers\MailgunProvider;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$receiver = app('receiver');
$receiver->extend('mailchimp', function ($app) {
return new MailchimpProvider(
config('services.mailchimp.webhook_secret')
);
});
$receiver->extend('mailgun', function ($app) {
return new MailgunProvider(
config('services.mailgun.webhook_secret')
);
});
}
}
Receiver needs two pieces of information to receive and handle webhook events:
name
data
Since these are found in different attributes or headers depending on the webhook, Receiver makes it simple ways to define them in your provider.
<?php
namespace Receiver\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class CustomProvider extends AbstractProvider
{
/**
* @param Request $request
* @return string
*/
public function getEvent(Request $request): string
{
return $request->input('event.name');
}
/**
* @param Request $request
* @return array
*/
public function getData(Request $request): array
{
return $request->all();
}
}
The getEvent()
method is used to return the name of the webhook event, ie. customer.created
.
The getData()
method is used to return the payload of data that can be used within your handler. By default this is set to $request->all()
.
Many webhooks have ways of verifying their authenticity as they are received, most commonly through signatures or basic authentication. No matter the strategy, Receiver allows you to write custom verification code as necessary. Simply implement the verify
method in your provider and return true or false if it passes.
A false
return will result in a 401 response being returned to the webhook sender.
<?php
namespace Receiver\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class CustomProvider extends AbstractProvider
{
public function verify(Request $request): bool
{
// return result of verification
}
}
Some webhooks want to perform a "handshake" to check if your endpoint exists and returns a valid response when it's first set up. To facilitate this, implement the handshake
method in your provider:
<?php
namespace Receiver\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class CustomProvider extends AbstractProvider
{
public function handshake(Request $request): array
{
// return result of handshake
}
}
Unlike the verify
method, handshake
expects an array to be returned, since many times the webhook sender is expecting a specific "challenge" response. The return of the handshake method is sent back to the webhook sender.
Have you created a custom Receiver? Share it with the community in our Receivers Discussion topic!
Made with contributors-img.
The MIT License (MIT). Please see License File for more information.