Webhooks
Receive and handle Paddle webhook events with signature verification, IP allowlisting, and idempotency.
Webhooks
The package registers an HTTP controller at the configured webhookPath (default: /webhooks/paddle). Every incoming request is:
- Verified against the notification secret (HMAC signature + replay window).
- Checked against the Paddle IP allowlist (if enabled).
- Deduplicated by Paddle event ID.
- Dispatched to all registered handlers.
Registering a Handler
Implement PaddleWebhookHandlerInterface and tag it vortos_paddle.webhook_handler, or rely on autoconfiguration:
use Vortos\Paddle\Webhook\PaddleWebhookHandlerInterface;
use Vortos\Paddle\Webhook\Event\SubscriptionActivatedEvent;
final class OnSubscriptionActivated implements PaddleWebhookHandlerInterface
{
public function supports(object $event): bool
{
return $event instanceof SubscriptionActivatedEvent;
}
public function handle(object $event): void
{
// $event->subscriptionId, $event->customerId, etc.
}
}Autoconfiguration picks up any service implementing PaddleWebhookHandlerInterface. No manual tagging required.
Signature Verification
Paddle signs every webhook with a notification secret. The verifier checks the Paddle-Signature header and rejects requests outside the configured replay window (default: 5 seconds).
$config
->notificationSecret($_ENV['PADDLE_NOTIFICATION_SECRET'])
->security()
->replayWindowSeconds(5);Always use a real secret in production
An empty or missing notification secret disables signature verification. Set PADDLE_NOTIFICATION_SECRET in every environment.
IP Allowlist
Paddle publishes a list of IP addresses it uses to send webhooks. Enable enforcement:
$config->security()->enforceIpAllowlist(true);When mode=sandbox, sandbox IPs are automatically included — no additional configuration required.
Idempotency
Each received Paddle event ID is stored in paddle_webhook_idempotency. Duplicate deliveries within the TTL window (default: 72 hours) are silently dropped. This protects handlers from double-processing on Paddle retries.
Disabling Webhooks
$config->webhooks()->enabled(false);This skips registering the controller route and all webhook infrastructure.
Prune Idempotency Records
php bin/console vortos:paddle:webhook:idempotency:pruneRun this periodically (daily via cron is sufficient) to remove expired records.