devmatchable/whop-symfony-bundle
Symfony 7 bundle for the Whop PHP SDK. Autowires WhopApiClient and WebhookVerifier from config, provides a ready-to-use (overridable) webhook controller/route, and supports sandbox base URL and custom HTTP client selection.
Install the package:
composer require devmatchable/whop-symfony-bundle
Ensure your project uses Symfony 7 and PHP 8.4+.
Configure the bundle (config/packages/whop.yaml):
whop:
api_key: '%env(WHOP_API_KEY)%'
webhook_secret: '%env(WHOP_WEBHOOK_SECRET)%'
base_url: 'https://api.whop.com/api/v1' # Use sandbox URL for testing
Set environment variables in .env:
WHOP_API_KEY=your_api_key_here
WHOP_WEBHOOK_SECRET=your_webhook_secret_here
Verify the route is auto-registered (default: /_whop/webhook). Test by sending a webhook to this endpoint.
Autowire the WhopApiClient in a service:
use Matchable\Whop\WhopApiClient;
final readonly class PaymentService
{
public function __construct(private WhopApiClient $whop) {}
public function getPayment(string $paymentId): array
{
return $this->whop->payments->get($paymentId)->toArray();
}
}
WhopApiClient anywhere to access Whop’s API methods (e.g., payments, customers).http_client in config to use a specific PSR-18 client:
whop:
http_client: App\Client\CustomHttpClient
Zero-config event listener (default behavior):
Subscribe to WhopWebhookReceivedEvent:
use Matchable\Whop\Bundle\Event\WhopWebhookReceivedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener]
final class WebhookListener
{
public function __invoke(WhopWebhookReceivedEvent $event): void
{
$payload = $event->payload; // Decoded webhook data
// Process payload (e.g., update database, trigger actions)
}
}
Custom handler (override default behavior):
WhopWebhookHandlerInterface:
use Matchable\Whop\Bundle\Webhook\WhopWebhookHandlerInterface;
final class CustomWebhookHandler implements WhopWebhookHandlerInterface
{
public function handle(array $payload, string $rawPayload): void
{
// Custom logic (e.g., validate, dispatch to services)
}
}
config/services.yaml:
services:
Matchable\Whop\Bundle\Webhook\WhopWebhookHandlerInterface:
alias: App\CustomWebhookHandler
Decorate the handler (wrap default logic):
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
#[AsDecorator('Matchable\Whop\Bundle\Webhook\WhopWebhookHandlerInterface')]
final class LoggingWebhookHandler implements WhopWebhookHandlerInterface
{
public function __construct(private WhopWebhookHandlerInterface $decorated) {}
public function handle(array $payload, string $rawPayload): void
{
// Log before/after delegating to the decorated handler
$this->decorated->handle($payload, $rawPayload);
}
}
TestKernel from the bundle’s tests to mock webhook requests.base_url to https://sandbox-api.whop.com/api/v1 in config for testing..env variables for WHOP_API_KEY and WHOP_WEBHOOK_SECRET to avoid hardcoding.Webhook signature validation:
401).webhook_secret matches Whop’s configured secret (supports whsec_ or ws_ prefixes).Payload structure:
array<string, mixed>. Map them to your domain types inside listeners/handlers (the SDK does not provide DTOs for incoming events).Route conflicts:
/_whop/webhook) must not clash with existing routes. Customize webhook_path if needed:
whop:
webhook_path: '/custom/webhook/path'
HTTP client dependency:
http_client, ensure the service implements PSR-18 (or Symfony’s HttpClientInterface).error_log(print_r($event->payload, true));
try-catch with WhopApiClient to handle SDK exceptions:
try {
$payment = $this->whop->payments->get($id);
} catch (\Matchable\Whop\Exception\WhopException $e) {
// Handle errors (e.g., 404, 403)
}
Custom DTOs:
PaymentCreatedDto) and map them in listeners:
$dto = new PaymentCreatedDto(
$event->payload['id'],
$event->payload['amount']
);
Event subscribers:
WhopWebhookReceivedEvent to add custom properties or methods:
final class CustomWebhookEvent extends WhopWebhookReceivedEvent
{
public function isSuccessful(): bool
{
return $this->payload['status'] === 'completed';
}
}
Middleware:
WhopApiClient to add middleware (e.g., logging, retries):
services:
Matchable\Whop\WhopApiClient:
decorate: true
arguments:
$decorated: '@Matchable\Whop\WhopApiClient.inner'
api_key and webhook_secret are mandatory. The bundle validates config at compile time..env variables. For manual setup, register the bundle in config/bundles.php and import routes:
# config/routes/whop.yaml
whop:
resource: '@WhopBundle/config/routes.php'
How can I help you explore Laravel packages today?