DI and actions
The facade is the recommended default, not the only way in. RoundlyConsulting\Webhooks\WebhooksManager is the facade’s root — a scoped binding you can inject — and every facade call ends in one action. All three styles run the same code:
// Send an event
Webhooks::send($endpoint, 'invoice.paid', ['invoice' => 42]);
$webhooks->send($endpoint, 'invoice.paid', ['invoice' => 42]); // WebhooksManager $webhooks, injected
app(SendWebhookAction::class)->execute($endpoint, 'invoice.paid', ['invoice' => 42]);
// Create an inbound endpoint
$created = Webhooks::inbound()->create(handler: 'billing');
$created = $webhooks->inbound()->create(handler: 'billing');
$created = app(CreateInboundEndpointAction::class)->execute(null, 'billing');
// Run the inbound pipeline (what the route does)
$response = Webhooks::inbound()->receive($key, $request);
$response = $webhooks->inbound()->receive($key, $request);
$response = app(ReceiveWebhookAction::class)->execute($key, $request);
// Redeliver
Webhooks::outbound()->redeliver($delivery);
$webhooks->outbound()->redeliver($delivery);
app(RedeliverWebhookAction::class)->execute($delivery);Inject the manager
use RoundlyConsulting\Webhooks\Models\OutboundDelivery;
use RoundlyConsulting\Webhooks\Models\OutboundEndpoint;
use RoundlyConsulting\Webhooks\WebhooksManager;
final readonly class InvoiceWebhooks
{
public function __construct(private WebhooksManager $webhooks) {}
public function paid(OutboundEndpoint $endpoint, int $invoice): OutboundDelivery
{
return $this->webhooks->send($endpoint, 'invoice.paid', ['invoice' => $invoice], id: "invoice-{$invoice}-paid");
}
}The fake extends the manager, so an injected WebhooksManager is the fake too under Webhooks::fake(). The manager’s other public methods (createInboundEndpoint(), rotateOutboundSecret(), redeliverFailed() and the rest) are internal — the sub-accessors call them so the fake sees every call. Use the sub-accessors.
Call an action
use RoundlyConsulting\Webhooks\Actions\Inbound\CreateInboundEndpointAction;
use RoundlyConsulting\Webhooks\Actions\Outbound\SendWebhookAction;
app(SendWebhookAction::class)->execute($endpoint, 'invoice.paid', ['invoice' => 42]);
app(CreateInboundEndpointAction::class)->execute($team, 'billing'); // the owner comes firstEvery host-facing action is final readonly, resolved from the container, with one execute(). The actions skip the fake and the owner scope — they take what you hand them — so use them to compose your own use cases, and the facade or the manager everywhere else.
Facade method → action
Namespaces are relative to RoundlyConsulting\Webhooks\Actions:
| Facade method | Action | execute() |
|---|---|---|
inbound()->create() | Inbound\CreateInboundEndpointAction | ?Model $owner = null, ?string $handler = null, string $scheme = 'default', ?string $name = null, ?string $secret = null, ?InboundEndpointOptions $options = null |
inbound()->update() | Inbound\UpdateInboundEndpointAction | InboundEndpoint $endpoint, InboundEndpointChanges $changes |
inbound()->enable() · disable() | Inbound\SetInboundEndpointStateAction | InboundEndpoint $endpoint, bool $enabled |
inbound()->delete() | Inbound\DeleteInboundEndpointAction | InboundEndpoint $endpoint |
inbound()->rotateSecret() | Inbound\RotateInboundSecretAction | InboundEndpoint $endpoint, ?CarbonInterface $grace = null, ?string $secret = null |
inbound()->receive() | Inbound\ReceiveWebhookAction | string $key, Request $request |
inbound()->verify() | Inbound\VerifyInboundRequestAction | InboundEndpoint $endpoint, Request $request, ?string $body = null, bool $deferCommit = false |
inbound()->replay() | Inbound\ReplayInboundDeliveryAction | InboundDelivery $delivery |
outbound()->create() | Outbound\CreateOutboundEndpointAction | string $url, array $events = ['*'], ?string $name = null, array $headers = [], string $scheme = 'default', ?OutboundEndpointOptions $options = null, ?Model $owner = null |
outbound()->update() | Outbound\UpdateOutboundEndpointAction | OutboundEndpoint $endpoint, OutboundEndpointChanges $changes |
outbound()->enable() · disable() | Outbound\SetOutboundEndpointStateAction | OutboundEndpoint $endpoint, bool $enabled |
outbound()->delete() | Outbound\DeleteOutboundEndpointAction | OutboundEndpoint $endpoint |
outbound()->rotateSecret() | Outbound\RotateOutboundSecretAction | OutboundEndpoint $endpoint, ?CarbonInterface $grace = null |
send() | Outbound\SendWebhookAction | OutboundEndpoint $endpoint, string $eventType, array|Arrayable|JsonSerializable $payload, ?string $id = null |
broadcast() · for($owner)->broadcast() | Outbound\BroadcastWebhookAction | string $eventType, array|Arrayable|JsonSerializable $payload, ?Model $owner = null |
outbound()->ping() | Outbound\PingEndpointAction | OutboundEndpoint $endpoint |
outbound()->redeliver() | Outbound\RedeliverWebhookAction | OutboundDelivery $delivery |
outbound()->redeliverFailed() | Outbound\RedeliverFailedDeliveriesAction | OutboundEndpoint $endpoint, ?CarbonInterface $since = null, bool $includeCancelled = false |
outbound()->dispatchDue() | Outbound\DispatchDueDeliveriesAction | — |
prune() | PruneAction | ?PruneOptions $options = null |
Two notes: CreateInboundEndpointAction takes the owner first, and VerifyInboundRequestAction also accepts the raw body when it was already read and $deferCommit — true hands you the verdict’s commit closure to run before you act. AcceptInboundDeliveryAction, ProcessInboundDeliveryAction, AttemptDeliveryAction, ScheduleRetryAction, RecordEndpointFailureAction, DisableOutboundEndpointAction and CancelPendingDeliveriesAction are internal building blocks.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.