hotmeteor/receiver
Receiver is a Laravel package for accepting and handling incoming webhooks. Define endpoints, verify provider signatures, and route payloads to events or jobs, making integrations with Stripe, GitHub, and similar services simple and secure.
Architecture fit
Receiver is a webhook ingress library for Laravel: it resolves an inbound HTTP request to a named provider, verifies the signature, normalizes the event, and dispatches a handler class. That is a narrow, well-bounded responsibility — it sits at the edge of the application and does not reach into domain logic, persistence, or queue internals. For any Laravel service that ingests third-party webhooks, this replaces a recurring class of bespoke, subtly-wrong verification code (timing-safe comparison, raw-body handling, header casing, replay windows) with a maintained abstraction. The v1.0.0 release strengthens the case: five new first-party providers (Shopify, Twilio, Mailchimp, SendGrid ECDSA, Paddle with key rotation) and a receiver:make --provider scaffold that emits a companion ServiceProvider for auto-discovery means we are no longer locked into the maintainer's provider list. Extending coverage to an in-house or niche vendor is now an afternoon task that produces a packageable artifact rather than a private fork.
The multi-event-per-request change (getEvent() returning an array of ['event_name' => $eventData] pairs, with one handler dispatched per event) is the most architecturally significant item. It acknowledges that batch payloads are the norm, not the exception, particularly for SendGrid. But it also changes the contract of a core method's return type — nominally additive, practically a shape change that every custom provider must be audited against. Any handler that assumed a single event, or any provider that returns a bare event, is now in a compatibility grey zone. "No breaking changes" is accurate at the semver level and optimistic at the code level.
Integration feasibility
High. It is a Composer package with Laravel auto-discovery, configuration lives in config/services.php, and there is no infrastructure dependency — no broker, no sidecar, no external service. A single engineer can have a proof of concept verifying one provider end-to-end in under a day, and the rewritten README with per-provider config examples and a handler naming table removes most of the guesswork that would otherwise consume that day. The ips, headers, and auth strategies for Postmark — now combinable — give us a defensible answer to the security review question "how do you know this request came from Postmark?" without writing custom middleware.
The friction points are real but bounded. First, verification correctness is only as good as the raw request body; Laravel middleware that mutates or re-encodes the payload will silently break HMAC verification, and this is the single most common integration failure in webhook work. Second, the naming convention that maps event strings to handler classes is now documented but still a convention — PAYMENT.AUTHORIZATION.CREATED → PaymentAuthorizationCreated is predictable, and the lowercasing fix addresses a genuine bug, but teams will still be surprised by collisions where two distinct provider events normalize to the same class name. Third, Paddle's key rotation implies our config and secret storage must support multiple active signing keys and a rotation cadence; that is an operational commitment the library surfaces but cannot discharge for us.
Technical risk Moderate and mostly contained. The dominant risk is supply chain, not runtime: a single-maintainer package sitting directly in the unauthenticated attack surface of the application. A verification bypass, a permissive default, or an unhandled exception path that returns 200 is materially worse than a bug in a view layer. Mitigations exist and are cheap: pin the version, review the verification code paths for each provider we enable, and ensure failures return non-2xx so senders retry.
The second risk is the v1.0.0 milestone itself. A 1.0 designation invites the assumption of API stability, yet the multi-event return type change demonstrates that core contracts are still settling — getEvent() changing shape at 1.0 is a signal to pin tightly and read minor release notes. The sendgrid ECDSA verification being opt-in is the right call for backwards compatibility, but it means the secure path is not the default path, and a team that enables SendGrid without reading the README gets weaker verification than they believe they have.
The third risk is coverage gaps against a real provider portfolio. Five new providers is meaningful progress, but a typical SaaS company ingests from a longer tail — Stripe, GitHub, Slack, HubSpot, internal services — and each additional provider is either a supported path or a small custom package we now own. The CI matrix (Laravel 10–13 on PHP 8.2–8.4) plus PHPStan level 5 and Pint is credible quality signaling, but level 5 is mid-tier static analysis and the matrix is the maintainer's compatibility claim, not our tested configuration.
Key questions
Stack fit
Direct fit for a Laravel 10+ application on PHP 8.2+. Configuration belongs in config/services.php alongside other provider credentials, which keeps secret handling inside the existing config/env pipeline and out of ad hoc code. Handlers are ordinary classes resolved through the container, so they can be queued, can emit domain events, and can be tested with standard Laravel testing idioms. Nothing about the library dictates our domain model — it terminates at the handler boundary, which is exactly where we want third-party vocabulary translated into our own.
The one deliberate divergence: treat Receiver as ingress and verification, and keep deduplication, idempotency, ordering, and persistence in our own layer. Providers retry aggressively and deliver out of order; the library does not promise exactly-once and should not be asked to.
Migration path
Greenfield services adopt directly — install, publish config, add provider credentials, run receiver:make for the first handler, ship. Brownfield services should run in parallel rather than swap: route new provider webhooks through Receiver while existing bespoke endpoints keep serving, then migrate provider by provider as each is exercised in production. This avoids a big-bang cutover on the highest-risk, least-forgiving code path in the system.
Custom providers already written against the old single-event contract must be audited before upgrade. The mechanical change is to return the array-of-pairs shape; the semantic change is that one request may now yield N handler invocations, which means handlers must be idempotent per event, not per request. Budget this as the real migration cost — the library upgrade is minutes, the handler audit is days.
Compatibility
Laravel 10, 11, 12, and 13 across PHP 8.2–8.4 is broader than most packages commit to and covers our likely versions. Pin to ^1.0 with a lockfile commit, and treat minor bumps as requiring a changelog read rather than automatic merge. Confirm our actual PHP and Laravel versions appear in the tested matrix; if we run an untested combination, add a smoke test rather than assuming.
Provider-level compatibility is the quieter concern: signature schemes evolve (Paddle's key rotation exists precisely because keys rotate), and a provider changing its signing algorithm is a breaking change for us regardless of the package's semver.
Sequencing
receiver:make --provider and ship it as an internal Composer package so other teams inherit it rather than reimplementing.Maintenance
Ongoing cost is low but nonzero. The package needs version bumps tracked alongside Laravel upgrades, and our handlers need to track provider API changes — new event types, renamed events, deprecated payload fields. Handler naming is code, so provider vocabulary changes become our refactors. The new CI tooling (PHPStan level 5, Pint, composer run lint / lint:check / analyse) is a genuine operational dividend: it means we can adopt the same commands locally and in CI and catch integration issues before they reach the webhook endpoint. We should mirror those scripts into our own pipeline rather than inventing equivalents.
The heavier maintenance artifact is any custom provider we write. The --provider scaffold emitting a companion ServiceProvider makes these publishable, which is good hygiene, but it also means we now own a package with its own release cadence and consumers.
Support Support burden concentrates in two places: "why is this webhook 403ing" and "why didn't this event fire." Both are diagnosable, but only if we instrument. Log the provider, the resolved event name, the verification outcome, and the handler dispatched — without the payload secrets. The rewritten README with per-provider config examples and a handler naming table substantially reduces the "what should I name my class" class of support tickets, which is more valuable than it sounds: naming conventions are where onboarding friction silently accumulates.
Escalation path for library bugs is upstream issues against a small maintainer team. For anything in the verification path, assume we may need to patch locally and have a fork strategy documented before we need it.
Scaling Webhook ingress scales the way the rest of the Laravel app scales — horizontally behind a load balancer, with handlers pushed onto the queue. The critical scaling constraint is that verification must happen synchronously and cheaply (it is a hash), while handler work must be asynchronous. The multi-event dispatch change amplifies this: a single SendGrid batch now fans out to N handler jobs, so queue depth, per-event idempotency keys, and rate limiting to downstream systems all become first-class concerns. A batch of 500 events from one request is a thundering-herd risk if handlers are not queued with backpressure.
Signature verification itself is negligible CPU. The real cost is the downstream fan-out, and that is our design problem, not the library's.
Failure modes
Ramp-up A Laravel-proficient engineer is productive in half a day: install, configure one provider, generate a handler, see an event flow. The generated scaffold and rewritten README are the main reasons. The genuinely slow part is not the library but the surrounding discipline — establishing idempotency keys, choosing queue topology, defining retry and dead-letter behavior, and building observability into the ingress path. Budget one to two weeks for a team to go from first install to a production-grade, monitored, idempotent webhook pipeline for a handful of providers. That estimate is dominated by our own integration decisions; the library earns its place by removing the part that is easy to get subtly wrong and hard to notice when you have.
How can I help you explore Laravel packages today?