tapp/filament-webhook-client
Filament resource and policy for Spatie Webhook Client. Adds a WebhookCall resource to your Filament panel with configurable models, policies, navigation, and translations. Requires Spatie Webhook Client installed and configured.
This package is a thin UI layer: it adds a read-only Filament resource so you can eyeball the rows that Spatie Webhook Client already writes to your webhook_calls table. It does not receive, validate, process, or dispatch webhooks — Spatie does. Treat the install as "Spatie first, plugin second."
Install and configure Spatie Webhook Client (the README explicitly flags this as a prerequisite). You need the webhook_calls migration published and migrated, plus at least one profile in config/webhook-client.php with a signing secret and a WebhookProcessor. If the table doesn't exist, this plugin's resource will 500 on you.
Install the UI package, matching the Filament major:
composer require tapp/filament-webhook-client:"^4.0" # Filament 4.x / 5.x
# Filament 3.x → use 1.x instead
Publish the config so you have something to edit:
php artisan vendor:publish --tag="filament-webhook-client-config"
Register the plugin in your panel provider:
use Tapp\FilamentWebhookClient\FilamentWebhookClientPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
FilamentWebhookClientPlugin::make(),
]);
}
Where to look first: the generated config/filament-webhook-client.php maps four things — resources.WebhookCallResource, models.webhook-call, policies.webhook-call, and navigation.{sort,icon}. Everything you'll ever want to change lives there or in a subclass of the resource. Then open the panel and hit Webhook Calls in the sidebar: the list shows url/method/created_at, and the view page renders payload and headers as formatted JSON (a deliberate choice since v1.0.1, replacing the old KeyValueEntry).
First real use case: you've just wired up a Stripe/GitHub/Shopify endpoint and the signature check is failing or your processor is silently doing nothing. Fire a test webhook, then open the record in the panel to read the raw payload, headers, url, method, and — when processing blew up — the stored exception. That exception field is the single most valuable thing this package surfaces; it's Spatie's job-failure trace sitting in a column you'd otherwise be querying by hand.
The daily loop is "receive → inspect → fix processor." Your WebhookProcessor::process() throws, Spatie stores the call with the exception attached, and you read it in the panel. Keep the resource purely diagnostic — resist the urge to make it a workflow tool.
Restrict access via the policy, not the panel. The bundled WebhookCallPolicy allows viewAny and view for everyone who can reach the panel. In any multi-role app that's too loose:
// config/filament-webhook-client.php
'policies' => [
'webhook-call' => \App\Policies\WebhookCallPolicy::class,
],
class WebhookCallPolicy
{
public function viewAny(User $user): bool
{
return $user->hasRole('admin');
}
public function view(User $user, WebhookCall $call): bool
{
return $user->hasRole('admin');
}
}
Since the default policy grants no delete/update, the resource is effectively read-only unless you write those abilities yourself — which is usually the right call, because deleting a stored call destroys your only forensic record.
Subclass the resource when you need columns, filters, or a different nav label. The v4.1.2 fix ("Resource pages to use the class from the config file") means page classes now resolve through the configured resource class, so this override pattern actually works end to end:
namespace App\Filament\Resources;
use Tapp\FilamentWebhookClient\Resources\WebhookCallResource as BaseResource;
class WebhookCallResource extends BaseResource
{
// add a filter, change default sort, add a "replay" action, etc.
}
'resources' => [
'WebhookCallResource' => \App\Filament\Resources\WebhookCallResource::class,
],
Swap the model if you've extended Spatie's. A very common day-two change is adding a status or processed_at column to webhook_calls. If you did, point the config at your model so the resource's table queries the right class:
'models' => [
'webhook-call' => \App\Models\WebhookCall::class,
],
Rename the nav item. There's no label key in the published config — the label comes from translations. Publish them (--tag="filament-webhook-client-translations") and edit rather than trying to force it through navigation.
Multi-panel apps: register the plugin only on the panel where support staff live. Registering it on a customer-facing panel and relying on the policy to hide it is a worse pattern — the resource will still be discoverable to anyone who can guess the route, and policy mistakes become data leaks.
Pair it with your existing logging. The plugin shows you one call. When you need correlation across calls, a webhook_calls row, and your processor's own logs, keep the Spatie call id in your log context so you can pivot between the panel and your log viewer.
^4.0 is the only line that works with Filament 4/5; 1.x is Filament 3. Composer will happily resolve an incompatible pair if your constraints are loose, and you'll get opaque Livewire/Filament errors rather than an install failure. Pin explicitly.Prunable on the underlying WebhookCall early, or you'll be looking at a multi-GB table in a year.signing_secret match, does WebhookProcessor exist at the configured class, and is the request actually passing signature validation (invalid signatures are rejected before storage). Once the row exists, the plugin will show it.Gate::policy() for the webhook model in an AuthServiceProvider, the two can fight. Pick one registration path — the config key here is the cleaner one.livewire(ListWebhookCalls::class)->assertCanSeeTableRecords(...) works in tests, and the usual Filament table-debugging advice applies when columns look wrong after an upgrade (v4.1.2 reworked the default table).^4.0 bump.How can I help you explore Laravel packages today?