nails / module-webhooks
This is the "Webhooks" module for Nails.
Requires
- php: >=8.3
- ext-json: *
- nails/common: dev-develop
- nails/module-admin: dev-develop
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-28 14:45:45 UTC
README
This module brings webhook functionality to Nails. A component exposes an endpoint by adding a class under src/Webhooks that implements Nails\Webhooks\Interfaces\Webhook. The module discovers it, verifies the request, and records the outcome.
The implementation plan is in .agents/features/webhooks.md.
Handlers
The public URL is derived from the component package and the class path under Webhooks. \Nails\Invoice\Webhooks\PaymentNotification is served at /webhooks/nails/module-invoice/payment-notification. An app class \App\Webhooks\PostToChannel is served at /webhooks/app/post-to-channel.
namespace App\Webhooks; use Nails\Webhooks\Delivery; use Nails\Webhooks\Interfaces\Webhook; use Nails\Webhooks\Interfaces\Webhook\ProtectedWebhook; use Nails\Webhooks\Result; use Nails\Webhooks\Traits\Configurable; use Nails\Webhooks\Traits\Defaults; use Nails\Webhooks\Traits\SharedSecret; class PostToChannel implements Webhook, ProtectedWebhook { use Defaults; use Configurable; use SharedSecret; public function getLabel(): string { return 'Post to channel'; } public function getConfigFields(): array { return [ 'channel' => [ 'label' => 'Channel', 'rules' => 'required', ], ]; } protected function secret(Delivery $oDelivery): string { return (string) $oDelivery->instance()->secret; } public function handle(Delivery $oDelivery): Result { $sChannel = (string) $this->config($oDelivery, 'channel'); return Result::accepted('Posted to ' . $sChannel); } }
Configurable means each admin instance supplies its own channel, secret, and URL token. Without that trait the handler is a single endpoint, which is the shape a payment driver uses for one platform account.
SharedSecret compares the X-Webhook-Token header. SignsPayload checks an HMAC of the raw body, including Stripe's t=<unix>,v1=<hex> header when signsTimestamp() returns true.
A repeat of a request that already succeeded is recorded as ignored and handle() is not called again. Use Nails\Webhooks\Traits\AllowsDuplicates when a repeat should run anyway.
Payment notifications
driver-invoice-stripe would add src/Stripe/Webhooks/PaymentNotification.php. The derived URL is /webhooks/nails/driver-invoice-stripe/payment-notification. The handler uses SignsPayload in timestamp mode with the header Stripe-Signature, and handle() completes or refunds the payment. That class lives in the driver, not in this module.