Search by

themusicdev / eventbrite

TheMusicDev

Eventbrite API v3 client - PSR-18 HTTP, PSR-16 cache, framework agnostic

Package info

github.com/TheMusicDev/eventbrite

pkg:composer/themusicdev/eventbrite

Statistics

Installs: 35

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 2

v0.2.0 2026-11-06 19:29 UTC

This package is auto-updated.

Last update: 2026-11-06 19:36:10 UTC


README

Eventbrite API v3 client — PSR-18 HTTP, PSR-16 cache, framework agnostic MIT code.

A small client for the Eventbrite API v3: read events, attendees, orders and ticket classes, manage webhooks, and run the OAuth authorization-code flow — with no framework lock-in.

Why this package exists: the official eventbrite/eventbrite-sdk-php has had no release since 2019, and the popular community client is abandoned — modern PHP (8.2+) had no maintained, framework-agnostic Eventbrite client.

  • PSR-18 HTTP client in (Guzzle, Symfony HTTP Client, WordPress-anything)
  • PSR-16 cache optional — GET responses cached per token (10 min default)
  • PSR-3 logger optional — warnings on 429 backoff
  • Continuation pagination exposed as generators
  • Typed exceptions carrying status code + Eventbrite's error body
  • 429 retries with exponential backoff (1s, 2s, 4s, then throws)
  • Webhook helpers: list, create, ensure (idempotent reconcile), delete

Install

composer require themusicdev/eventbrite

Requirements: PHP 8.2+, a PSR-18 client and PSR-17 factories. With Guzzle:

composer require guzzlehttp/guzzle

Getting an API key

  1. Create/Sign in to an Eventbrite account → account settings → Developer Links → Developer Portal → Get a Free API Key.
  2. That page shows:
    • API key — the OAuth client_id
    • Client secret — for OAuth server-side code exchange
    • Private token — a personal bearer token for your own account (handy to start)
  3. Full walkthrough: docs/getting-an-api-key.md.

Quickstart — private token

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Psr7\HttpFactory;
use TheMusicDev\Eventbrite\EventbriteClient;

$httpFactory = new HttpFactory(); // PSR-17 request + stream factory

$client = new EventbriteClient(
    new HttpClient(),
    $httpFactory,
    $httpFactory,
    $_ENV['EVENTBRITE_TOKEN'], // never hard-code; see docs/getting-an-api-key.md
);

$me              = $client->me();                          // GET /users/me/
$organizations   = $client->organizations();               // GET /users/me/organizations/
$orgId           = $organizations['organizations'][0]['id']
    ?? throw new RuntimeException('This Eventbrite account has no organization.');
$allEvents       = $client->organizationEvents($orgId);    // Generator over pages
foreach ($allEvents as $event) {
    // ...
}

organizations() can come back empty. Eventbrite's docs say the user id then works as the organization id, but it returned 404 in testing (2026-11-05), so create an organization in the Eventbrite dashboard first.

Caching

Pass any PSR-16 cache; GET responses cache per token for 600s (configurable $cacheTtl, 0 disables). The webhooks listing is never cached (it is the enabled-state source of truth). With symfony/cache:

use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Adapter;

$cache = new Psr16Adapter(new FilesystemAdapter('eventbrite', 600));

$client = new EventbriteClient(new HttpClient(), $httpFactory, $httpFactory,
    $token, $cache);

Query the API

Every response method returns decoded JSON arrays; non-2xx throws EventbriteApiException (getStatusCode(), getErrorBody()).

try {
    $client->event('123456');
} catch (EventbriteApiException $e) {
    // $e->getStatusCode(); $e->getErrorBody();
}

// one page: GET /organizations/{id}/events/ with any query params
$page = $client->get('organizations/' . $orgId . '/events/', ['time_filter' => 'all']);

// paginated generators (continuation-based)
$attendees = $client->eventAttendees('123456');
$orders    = $client->eventOrders('123456');
foreach ($attendees as $attendee) { /* attendee['profile']['email'], ['affiliate'] ... */ }

Webhooks

// list (never cached)
$hooks = $client->webhooks($orgId)['webhooks'];

// ensure an endpoint has one webhook per action — idempotent, run freely
$client->ensureWebhooks($orgId, 'https://example.com/webhook', [
    'attendee.updated', 'barcode.checked_in', 'order.placed', 'event.published',
]);

// remove the ones pointing at an endpoint
$client->disableWebhooks($orgId, 'https://example.com/webhook');

Deliveries POST {"api_url": ".../v3/orders/123/"} to your endpoint — fetch the pointed object with fetchUrl() (bypasses the cache, so you never store a pre-change snapshot) and upsert. Validate the URL: Eventbrite webhooks carry no signature, so your receiver must refuse anything outside https://www.eventbriteapi.com/v3/. Never return 410 — it auto-unsubscribes your endpoint.

OAuth (authorization code flow)

use TheMusicDev\Eventbrite\EventbriteOAuth;

$oauth = new EventbriteOAuth($httpClient, $httpFactory, $httpFactory,
    $clientId, $clientSecret,         // API key page
    'https://example.com/oauth/eventbrite/callback', // registered redirect URI
    $appSecret,                       // any stable app secret, signs the state
);

session_start();

// 1. redirect the browser to:
$oauth->authorizeUrl($oauth->newState(session_id()));

// 2. on the callback (?code=...&state=...):
if ($oauth->verifyState($state, session_id())) {
    $tokenResponse = $oauth->exchangeCode($code); // ['access_token' => ...]
    // store $tokenResponse['access_token'] and pass it to the client
}

The state is signed and stored nowhere, but it is bound to the visitor value, so a state minted for one browser fails in another. That blocks login CSRF, where an attacker gets a victim to open the attacker's own callback URL. Any value that identifies the browser works, as long as it survives until the callback:

  • Don't regenerate the session id mid-flow.
  • SameSite=Strict session cookies aren't sent on the redirect back from Eventbrite; use Lax.

States expire after 10 minutes ($maxAge on verifyState() changes this) and are not single-use.

Testing your integration

tests/Support/FakeHttp.php is a recording, rotating PSR-18 client + PSR-17 factories in one object. Composer doesn't autoload it for consumers, so require it from vendor/themusicdev/eventbrite/tests/Support/FakeHttp.php or copy it, and run composer require --dev guzzlehttp/psr7, which it needs. See tests/ for usage.

Docs

Quality

  • PHPStan (max + strict-rules), PHP-CS-Fixer (PSR-12), Pest tests + coverage on CI (PHP 8.2–8.4)
  • composer-require-checker guards the PSR-only dependency list

License

MIT