Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Twitch Response Bundle Laravel Package

mrgoodbytes8667/twitch-response-bundle

A Laravel/PHP package for integrating Twitch API responses into your application. It provides structured response handling to help standardize Twitch API data and simplify building Twitch-powered features.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it is a Symfony bundle, not a Laravel package. In a Laravel app you will use it as a plain library — Composer autoloads the classes fine, you simply never register the bundle in config/app.php and never get the Symfony DI wiring. If a class needs a Symfony service, you instantiate it yourself or bind it in a service provider.

Minimal steps

  1. composer require mrgoodbytes8667/twitch-response-bundle — current release is v0.7.6. Still 0.7.x, still pre-1.0, so pin it exactly (see gotchas) rather than floating on ^0.7.
  2. Open vendor/mrgoodbytes8667/twitch-response-bundle/composer.json and note the autoload.psr-4 root (typically MrGoodBytes8667\…) plus the require block. Confirm what it drags in — symfony/* components especially, and whether symfony/serializer is a hard or dev requirement. The normalizers only work if the Serializer is actually installed.
  3. ls vendor/mrgoodbytes8667/twitch-response-bundle/src/ — this package is small enough to read end to end in one sitting, and you should. You are looking for four groups now, not three: response/DTO classes, backed enums, the signature-verification class/service, and a Normalizer/ directory (added to getSupportedTypes() in v0.7.6).
  4. Note the classes you actually want in your own app/ layer (see below) before writing anything — don't let framework-adjacent code call the vendor namespace directly from controllers.

First use case: EventSub webhook receiver. This is where the package earns its place, because the signature verification is fiddly and easy to get subtly wrong. A minimal route plus a verification middleware is the whole day-one win:

// routes/api.php
Route::post('/webhooks/twitch', TwitchWebhookController::class)
    ->middleware('twitch.signature');
// app/Http/Middleware/VerifyTwitchSignature.php
public function handle(Request $request, Closure $next)
{
    $id        = $request->header('Twitch-Eventsub-Message-Id');
    $timestamp = $request->header('Twitch-Eventsub-Message-Timestamp');
    $signature = $request->header('Twitch-Eventsub-Message-Signature'); // "sha256=..."
    $body      = $request->getContent(); // raw, unparsed

    $expected = 'sha256=' . hash_hmac('sha256', $id . $timestamp . $body, config('services.twitch.webhook_secret'));

    abort_unless(hash_equals($expected, (string) $signature), 403);
    // also reject stale timestamps (Twitch recommends ~10 min tolerance)

    return $next($request);
}

If the package already exposes a verifier object, wrap it in one of your own classes so the rest of your app depends on your interface, not the vendor's.

Second thing to try, now that v0.7.6 landed: hydrate one Helix response through the package's normalizers by hand (php artisan tinker is fine). If that round-trips cleanly, the DTOs become genuinely usable in Laravel; if it doesn't, you've learned in ten minutes that the typed objects are decoration and you should stay on arrays. See the Serializer pattern below.

Implementation Patterns

Adapter layer over the DTOs. Do not sprinkle vendor DTOs through your app. Create app/Twitch/ with a thin mapping layer (TwitchUser, TwitchStream, your own enums) and convert at the boundary. This costs an hour now and is the difference between swapping this package out in an afternoon versus a week. It matters more than usual here: zero stars, zero dependents, single maintainer — actively patched (v0.7.6 is the seventh patch in the 0.7 line) but with no ecosystem safety net.

Standalone Serializer for Helix responses. The v0.7.6 change is the tip-off: the package ships Symfony Serializer normalizers, not hand-written fromArray() factories. That means in Laravel you don't need the bundle — you need a Serializer with those normalizers registered, built once in a service provider and bound as a singleton:

// AppServiceProvider::register()
$this->app->singleton('twitch.serializer', function () {
    $normalizer = new \MrGoodBytes8667\TwitchResponseBundle\Normalizer\TwitchUserNormalizer();

    return new \Symfony\Component\Serializer\Serializer(
        [$normalizer],   // normalizers (object -> array)
        [$normalizer],   // denormalizers (array -> object)
    );
});
$user = app('twitch.serializer')->denormalize(
    $response->json('data.0'),
    \MrGoodBytes8667\TwitchResponseBundle\Response\TwitchUser::class,
    'json',
);

Check the normalizer constructors before you write this — if any take arguments, wire them here, not in the controller. Keep this behind your own TwitchResponseHydrator interface so the app never type-hints the vendor class.

EventSub: verify → ack → queue. Twitch retries aggressively. Verify the signature, dedupe on Twitch-Eventsub-Message-Id via Cache::add($id, true, now()->addHours(24)), return 204 immediately, and push the payload to a job. Never do Twitch API calls or DB writes inside the webhook request. Handle the webhook_callback_verification message type by returning the raw challenge field after signature verification.

Typed payload handling. Decode the verified JSON into whatever event/response classes the package provides, then switch on the enum for the subscription type:

$type = TwitchEventType::tryFrom($payload['subscription']['type']);

match ($type) {
    TwitchEventType::STREAM_ONLINE  => StreamWentOnline::dispatch($payload),
    TwitchEventType::STREAM_OFFLINE => StreamWentOffline::dispatch($payload),
    default                         => Log::info('unhandled eventsub type', ['type' => $payload['subscription']['type']]),
};

Always use tryFrom() with a default arm. Twitch adds subscription types and enum values without warning, and a hard from() will throw in production on a payload you've never seen.

Enum as validation source. Backed enums are the useful half of this package. Rule::enum(TwitchUserType::class) (Laravel 9+) and $enum->value at the serialization boundary give you free type safety when building filters/forms over Twitch concepts like broadcaster type, subscription status, or sort order.

Symfony shops. If the same codebase is Symfony, register the bundle in bundles.php, let the extension wire the verifier and normalizers, and inject them. Note that the normalizers' getSupportedTypes() support means they're ready for Symfony 6.3+/7's normalizer contract, so the bundle path is the better-supported one — if you're dual-framework, that's where the package is actually maintained. The Laravel and Symfony usages should share the adapter layer, not the raw vendor classes.

Testing. Store a handful of real, scrubbed webhook bodies as fixtures under tests/Fixtures/twitch/. Round-trip them through your verifier (including a bad-signature case and a stale-timestamp case) and through your DTO mapping. That fixture set is also your regression net when you eventually replace the package — and it's the only thing that will catch a normalizer change on a patch bump.

Gotchas and Tips

  • Raw body, or nothing works. Signature verification is over the exact bytes. Any middleware that re-encodes JSON, or reading $request->all() and calling json_encode() on it, will break the HMAC. Verify in middleware before the route, and never touch getContent() afterwards in a way that assumes it's still available.
  • Concatenation order is fixed: message id + timestamp + body, HMAC-SHA256, hex, prefixed sha256=. Use hash_equals, not ===. This is the single most common source of "why does verification fail" — it's almost never the secret, it's the order or a stray newline in the secret from .env.
  • Reject stale timestamps. Signature-valid but days-old requests are replays. Compare the header timestamp to now() with a tolerance window and reject outside it.
  • getSupportedTypes() needs a recent Serializer. The v0.7.6 addition is required by Symfony 6.3+/7's NormalizerInterface/DenormalizerInterface. On older Symfony it's simply an ignored extra method (harmless); on Symfony 7 the previous releases would have been the problem, so 0.7.6 is effectively your minimum, not a nice-to-have. If you're pinning to a Symfony LTS, pin both together and test the upgrade as a pair.
  • Normalizer caching behaviour. What getSupportedTypes() returns determines whether Symfony caches "this normalizer supports class X". A catch-all entry (['*' => true]) disables that cache — irrelevant at webhook volumes, relevant if you start hydrating large Helix pages in a loop. Read the return value in src/Normalizer/ before you assume hydration is cheap.
  • Extra attributes are the silent loss. Denormalization ignores unknown keys by default, which is exactly what you want for forward-compatibility with Twitch's new fields — and exactly why the DTOs are lossy. Leave allow_extra_attributes at its default; do not set it to false to "catch drift", or a field Twitch adds next Tuesday turns into a 500.
  • DTOs are lossy. Twitch adds response fields faster than a small package gets updated. If the response objects don't model a field you need (pagination cursors, total, newer endpoint fields), you won't see it. Keep the raw array alongside the typed object until you've confirmed coverage.
  • Enum drift. Same problem, sharper edge: an unmapped value from tryFrom() returns null, which silently becomes "unknown" in your match. Log the raw value whenever you hit a default arm so you notice new Twitch values instead of quietly dropping events.
  • Nullability. Helix returns null for things like game_id/game_name on offline channels and for optional timestamps. The DTOs may declare these non-nullable or you may assume so — and with normalizers, a null hitting a non-nullable typed property is a TypeError, not a soft failure. Type-test against an offline channel and a brand-new account before trusting them.
  • Framework mismatch. Don't expect a service provider, config file, facade, or publishable assets. If the bundle's services depend on Symfony DI or symfony/http-client, construct them manually in AppServiceProvider and bind to interfaces. The normalizers are the easiest piece to use standalone — start there.
  • Dependency and maintenance risk. Zero stars, zero dependents, single maintainer, NOASSERTION license metadata (read the actual LICENSE file before shipping to a client). The upside versus the last review: the package is being patched — v0.7.6 lands a real forward-compatibility fix — so it isn't abandoned. That also means patch bumps can touch the normalizers you now depend on. Pin the exact version ("mrgoodbytes8667/twitch-response-bundle": "0.7.6", no caret), run composer audit, and treat every bump as needing your fixture round-trip test. Your mitigation is still the adapter layer: when it dies, you delete one directory and rewrite ~200 lines.
  • This is not an API client. It gives you response shapes, enums, normalizers, and verification — not authentication, token refresh, rate-limit handling, or HTTP calls. You still need Http::withToken() plus your own 429/Ratelimit-Reset backoff.
  • Debugging workflow. composer show mrgoodbytes8667/twitch-response-bundle for the resolved version and deps, grep -rn "function verify" vendor/mrgoodbytes8667/twitch-response-bundle/src to find the verifier, ls src/Normalizer/ to see what's actually hydratable, and php artisan tinker to probe enums (TwitchUserType::tryFrom('affiliate')) before wiring them into validation. Log the raw payload before decoding, once, on every event type you handle — it's the only way to see what the DTO silently dropped.
  • Extension points are thin. There's no obvious plugin surface in a package this size. If you need a missing field or enum case, the honest options are: fork it, subclass the normalizer in your app/ layer, or stop using it for that piece and rely on the raw array. The verification helper and the enums are the parts worth keeping even if you drop the rest.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect