devmatchable / whop-php-sdk
Framework-agnostic PHP SDK for the Whop API.
Requires
- php: ^8.4
- php-http/discovery: ^1.19
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.62
- infection/infection: ^0.29
- nyholm/psr7: ^1.8
- php-http/mock-client: ^1.6
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
Suggests
- nyholm/psr7: PSR-7/PSR-17 implementation
- symfony/http-client: PSR-18 client (or guzzlehttp/guzzle)
This package is auto-updated.
Last update: 2026-05-15 06:57:39 UTC
README
Warning
This package is in active development and is not yet ready for production use.
The public API may change at any time before version 1.0.0 is released.
Please do not depend on it in production projects until a stable release is published.
Framework-agnostic PHP client for the Whop API. Built on pure PSR interfaces (PSR-18 / PSR-17 / PSR-7) — drop it into any PHP 8.4+ project without pulling in a framework or opinionated HTTP stack.
Requirements
- PHP 8.4+
- A PSR-18 HTTP client and a PSR-7/17 implementation (your choice — see below)
Installation
composer require devmatchable/whop-php-sdk
The SDK declares only the PSR interface packages and php-http/discovery in its
require block. Concrete HTTP implementations are not bundled — you pick what fits
your stack.
Recommended: nyholm/psr7 (PSR-7 + PSR-17) paired with symfony/http-client
(PSR-18) work well together:
composer require nyholm/psr7 symfony/http-client
Any other PSR-18 client (guzzlehttp/guzzle, etc.) and any PSR-7/17 implementation
work as drop-in replacements.
Note
The SDK uses Symfony\Component\HttpClient\Psr18Client — the PSR-18 adapter shipped
by symfony/http-client. This is intentionally different from Symfony's framework-native
HttpClientInterface, which has a non-PSR shape and isn't compatible with this SDK (or
any other PSR-18 consumer). The two interfaces ship from the same package but solve
different problems: HttpClientInterface is for Symfony-native consumers; Psr18Client
bridges the same underlying client to the PSR-18 standard the SDK type-hints against.
Quick start
use Matchable\Whop\WhopApiClient; use Symfony\Component\HttpClient\Psr18Client; $client = new WhopApiClient( httpClient: new Psr18Client(), apiKey: $_ENV['WHOP_API_KEY'], ); // Methods that map to a typed DTO return it directly: $company = $client->companies->get('biz_xxxxxxxx'); echo $company->name; // Methods without a DTO return the decoded response array: $list = $client->companies->list(['page' => 1]);
PSR-17 request and stream factories are auto-discovered at construction time via
php-http/discovery when you omit them. Pass requestFactory: and streamFactory:
explicitly if you want to control which implementation is used.
Error handling
Every failure surfaces as a WhopException — network problems, non-2xx responses,
bad JSON, and shape mismatches all go through the same base type, so a single catch
covers everything:
use Matchable\Whop\Exception\WhopException; try { $payment = $client->payments->get('pay_xxxxxxxx'); } catch (WhopException $e) { // always safe to catch here echo $e->getMessage(); }
When you need to distinguish the failure mode, catch a specific subtype:
use Matchable\Whop\Exception\WhopApiException; use Matchable\Whop\Exception\TransportException; use Matchable\Whop\Exception\WhopException; try { $payment = $client->payments->get('pay_xxxxxxxx'); } catch (WhopApiException $e) { // Non-2xx response from the Whop API echo $e->statusCode; // int — HTTP status code print_r($e->responseBody); // array — decoded response body } catch (TransportException $e) { // Network-level failure (DNS, connection refused, etc.) echo $e->getMessage(); } catch (WhopException $e) { // Anything else: SerializationException, MissingArgumentsException echo $e->getMessage(); }
The five concrete exception types:
| Exception | When it's thrown |
|---|---|
WhopApiException |
The API returned an HTTP 4xx or 5xx response |
TransportException |
A network-level PSR-18 failure (connection refused, timeout, etc.) |
SerializationException |
The request body couldn't be JSON-encoded, or the response body couldn't be decoded |
MissingArgumentsException |
A required field was absent or invalid when hydrating a typed DTO |
WebhookVerificationException |
Webhook signature, timestamp, or header verification failed |
WhopApiException exposes two public readonly properties: statusCode (int) and
responseBody (array).
Configuration
The full constructor signature:
public function __construct( ClientInterface $httpClient, // PSR-18 client — required string $apiKey, // Whop API key — required string $baseUrl = 'https://api.whop.com/api/v1', // production default ?RequestFactoryInterface $requestFactory = null, // auto-discovered when null ?StreamFactoryInterface $streamFactory = null, // auto-discovered when null )
To target the Whop sandbox instead of production, pass the sandbox base URL:
$client = new WhopApiClient( httpClient: new Psr18Client(), apiKey: $_ENV['WHOP_SANDBOX_API_KEY'], baseUrl: 'https://sandbox-api.whop.com/api/v1', );
If requestFactory or streamFactory is omitted and no PSR-17 implementation is
installed, the constructor throws Http\Discovery\Exception\NotFoundException at
construction time.
Webhook verification
WebhookVerifier implements the Standard Webhooks
specification. Both whsec_ (production) and ws_ (sandbox) secret formats are
supported — construct it with whichever secret is configured in your Whop dashboard:
use Matchable\Whop\Exception\WebhookVerificationException; use Matchable\Whop\Webhook\WebhookVerifier; $verifier = new WebhookVerifier(webhookSecret: $_ENV['WHOP_WEBHOOK_SECRET']); // From raw body and headers: try { $verifier->verify($rawBody, $requestHeaders); } catch (WebhookVerificationException $e) { // signature mismatch, stale timestamp, or missing headers http_response_code(400); exit; } // From a PSR-7 request object: $verifier->verifyRequest($psr7Request);
$requestHeaders is a case-insensitive array mapping header names to values. The
verifier checks for webhook-id, webhook-timestamp, and webhook-signature and
enforces a 5-minute timestamp tolerance.
Resources
Access the API through resource properties on $client. Each method returns a typed
DTO where one exists, otherwise the raw decoded array:
$company = $client->companies->get('biz_xxxxxxxx'); // Company DTO $payment = $client->payments->get('pay_xxxxxxxx'); // Payment DTO $refund = $client->payments->refund('pay_xxxxxxxx'); // RefundResponse DTO $list = $client->payments->list(['page' => 1]); // array
The SDK covers the full Whop API surface across 57 resource groups. Per-endpoint method signatures and response shapes are documented in the GitHub Wiki.
A quick map of what's available:
| Group | Properties |
|---|---|
| Core | $companies, $accountLinks, $files, $accessTokens, $authorizedUsers, $users, $members, $webhooks |
| Payments & Billing | $payments, $checkouts, $plans, $products, $memberships, $refunds, $invoices, $promoCodes, $paymentMethods, $setupIntents |
| Platform & Finance | $transfers, $feeMarkups, $topups, $withdrawals, $ledgerAccounts, $payoutAccounts, $payoutMethods |
| Disputes | $disputes, $disputeAlerts, $resolutionCenter |
| Commerce | $shipments, $leads, $entries, $reviews, $affiliates, $stats |
| Experiences & Courses | $experiences, $courses, $courseChapters, $courseLessons, $courseLessonInteractions, $courseStudents |
| Communication | $chatChannels, $dmChannels, $dmMembers, $messages, $reactions, $forums, $forumPosts, $supportChannels, $notifications |
| Advertising | $adCampaigns, $adGroups, $ads |
| Apps & AI | $apps, $appBuilds, $aiChats, $tokenTransactions |
| Verifications | $verifications |
Development
composer test # PHPUnit composer stan # PHPStan composer cs # PHP-CS-Fixer dry-run composer cs:fix # PHP-CS-Fixer apply composer infection # Infection mutation testing
License
MIT — see LICENSE.