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

Webhook Laravel Package

symfony/webhook

Part of the Symfony ecosystem, symfony/webhook eases both sending and consuming webhooks in PHP applications. It provides building blocks for dispatching outgoing webhooks and validating incoming ones, integrating cleanly with Symfony's event and HTTP components.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. symfony/webhook is a small, framework-agnostic component that solves two distinct problems: inbound (a RequestParserInterface contract that turns a raw Request into one or more RemoteEvent value objects, plus signature-verification helpers) and outbound (a ClientInterface/TransportInterface + AbstractRequest for describing and dispatching a payload). Critically for us, the inbound half is genuinely Laravel-compatible with zero bridging code, because Illuminate\Http\Request extends Symfony\Component\HttpFoundation\Request. The outbound half has no Laravel-native entry point — in Symfony it is normally consumed through the Notifier's webhook transport — so adopting it in Laravel means writing our own transport wiring.

What we actually get vs. what the description claims. The vendor/description text advertises "retries, exponential backoff, idempotency." The component does not implement delivery guarantees; that is delegated to Messenger/queues/HttpClient in Symfony. For us this is the single most important scoping correction: adopt this for parsing, normalization, and signature verification, and budget delivery reliability as separate work on Laravel's queue. Sold internally as "webhook delivery infrastructure," it will underdeliver.

What changed in v8.1.7 (re-reviewed; published 2026-09-15). A single bug fix: a malformed request body is now rejected with 406 Not Acceptable instead of 500 Internal Server Error (#65772, backported across the 8.1.x patch line from 8.1.1 → 8.1.7). There are no API changes, no new features, no deprecations, and no breaking changes — RequestParserInterface, RemoteEvent, and the outbound contracts are untouched. Our architectural conclusion is unchanged. (Note: our earlier record carried the wrong publication date for this tag; the diff v8.1.1...v8.1.7 is still the same single commit, so nothing material follows from the correction beyond cadence framing below.)

The update is nonetheless worth recording for two operational reasons. First, it is a status-code correctness fix: a 500 on garbage input is a false signal of our instability, and providers that retry on 5xx will retry a payload that can never succeed. Reclassifying to 406 stops retry storms and separates "malformed/malicious input" from "our service is broken" in both metrics and alert routing. Second, and more important for us, it exposes a branch-tracking question: the fix is published on the 8.1 line, and v8.x requires PHP ≥ 8.4, while our current environments are on PHP 8.2/8.3 and therefore pinned to ^7.4. We must confirm whether the fix was backported to 7.4; if not, we are knowingly carrying the 500 behavior until our PHP floor moves.

Integration feasibility. Moderate-low effort, but entirely ours to build: one service provider binding parsers into the container, one controller/route (CSRF-exempt, throttled), a mapping from RemoteEvent to a Laravel event, and one parser class per provider. Realistic estimate: 3–5 dev-days for the first provider end-to-end including conformance tests, then ~0.5–1 day per additional provider. The component ships abstract PHPUnit test cases for parsers, which is a genuine quality lever — signature bugs fail silently, so a conformance kit is worth more than the runtime code. A single-line bug fix spanning six patch releases (v8.1.1 → v8.1.7) is also a useful signal in itself: the inbound code path is stable and low-churn, not a moving target we need to babysit.

Technical risk. Intrinsic risk is low: MIT, small surface (~a dozen classes), an active and predictable release train (6.4 LTS → 7.x → 8.x), and versioned changelogs. Adoption risk is the real concern: 0 Packagist dependents on this split repo and 56 stars mean essentially no Laravel community signal, no Laravel-oriented documentation, and no blog/StackOverflow corpus. The 0-dependents figure is misleading (most consumers install via symfony/symfony/FrameworkBundle, so the code is battle-tested) — but Laravel-side we are first movers and own all the glue permanently. Note also the version cliff: v8.x declares PHP ≥ 8.4, so on Laravel 11/12 with PHP 8.2/8.3 we pin ^7.4 — which as of v8.1.7 also means we are one branch behind on correctness fixes, not just on features. That is the cost of the PHP floor, and it is small but real.

Key questions to resolve before committing.

  • Which providers, and which signing schemes? How many parsers must we own ourselves (likely most non-Symfony ones)?
  • New: was the 406 fix (#65772) backported to the 7.4 branch? If not, do we cherry-pick/vendor-patch, accept 500-on-malformed until our PHP upgrade, or pull the PHP 8.4 upgrade forward?
  • What is our inbound 2xx latency SLO from providers? If < 2s, parsing must hand off to a queue immediately and the component is only the front door.
  • Do we need a multi-tenant endpoint/secret registry (per-customer webhooks)? That is not in scope and would be the larger build.
  • Do we need replay protection (timestamp tolerance + nonce) beyond HMAC? Not provided out of the box.
  • Who owns provider parser maintenance as a standing responsibility, and what is our patch-upstream SLA given issues go to symfony/symfony, not this repo?
  • What is the exit cost if we abandon it? (Low: interfaces are thin; a wrapper isolates us to a ~1–2 week swap.)

Integration Approach

Stack fit. Clean. The only hard contract is HttpFoundation, which Laravel already uses. There is no dispatcher conflict if we treat the component as a pure parser layer and re-emit results on Laravel's event bus (RemoteEvent → our own WebhookReceived event). Avoid pulling in Symfony's WebhookController/FrameworkBundle glue; write a ~40-line Laravel controller instead — which also means the 406-vs-500 decision is partly our controller's to make (see below).

Compatibility checks first.

  • composer why-not symfony/webhook ^7.4 against our locked symfony/http-foundation and symfony/http-kernel — Laravel pins these transitively, and adding the component can force minor bumps.
  • Confirm PHP floor per environment; do not let a ^8.0 constraint sneak in via a transitive requirement (PHP 8.4 only).
  • New: check the 7.4 changelog for #65772. Record the outcome either way — if absent, add an explicit error-handling branch in our adapter so malformed bodies surface as 400/406 from our controller regardless of what the parser throws. That is cheap insurance and removes a reason to care about the branch we are on.
  • Verify Laravel middleware does not mutate the raw body on webhook routes (see Failure Modes).

Migration path — strangler, not rewrite. Do not touch working endpoints. Inventory every existing inbound webhook and audit the hand-rolled HMAC code; that audit is arguably the highest-value output of this whole exercise, since it usually uncovers endpoints verifying signatures against re-encoded JSON or skipping verification entirely in non-prod. Then introduce the adapter and route only new providers through it.

Sequencing.

  1. Build the adapter (provider, route, event mapping, feature flag) and one parser. No behavior change.
  2. Shadow mode, 1–2 weeks: parse live traffic alongside the existing handler, log only, and diff RemoteEvent output against current processing. Gate on zero unexplained diffs.
  3. Cut over the single highest-value provider (typically payments/Stripe). Keep the old path dark for one release cycle.
  4. Add provider #2 as the team's reference implementation; document the golden path in our repo, since upstream docs are Symfony-only.
  5. Only then evaluate the outbound half — and only if we deliberately adopt the Notifier transport. Otherwise keep outbound on our own queue + Http::withHeaders, which we need anyway for backoff and dead-lettering.

Testing. Adopt the component's abstract parser test cases as a required base class for every parser, plus fixture replay of captured real payloads (valid, tampered body, stale timestamp, unknown event type). This makes signature regressions loud instead of silent. Add malformed-body fixtures explicitly — truncated JSON, wrong content type, oversized body, non-UTF-8 bytes — asserting the 406/400 path, since that is the one behavior that has actually changed under us.

Operational Impact

Maintenance. Component maintenance is near-zero and rides Symfony's predictable release train (6.4 LTS → 7.x → 8.x), which is a materially better upgrade story than an unknown vendor's. The v8.1.7 patch is a good example of the shape of change we should expect: rare, surgical, behavior-correcting rather than API-moving. Our maintenance is the adapters and the provider parsers — budget roughly 0.5 engineer-weeks per quarter for parser upkeep, secret rotation, schema drift from providers, and a periodic check that fixes landing on the 8.x branch have equivalents on our pinned branch.

Support. Issues belong to symfony/symfony, not this repo (it is a read-only subtree split), and a niche component may sit for weeks. No commercial support. Practical implication: we must be willing to vendor-patch or open PRs, and we should not adopt it for a revenue-critical path without one engineer able to read the component source.

Scaling. Inbound is stateless and cheap; the bottleneck is invariably downstream processing, so pair it with queued jobs and respond 2xx fast. Outbound via the component is synchronous — one slow customer endpoint blocks the request — so we must wrap it in jobs, at which point we are building the delivery layer ourselves and the outbound value proposition largely evaporates.

Failure modes to instrument from day one.

  • Raw-body mutation: any middleware that re-encodes JSON silently breaks HMAC. Highest-probability, hardest-to-diagnose failure.
  • Signature mismatch spikes (401): ambiguous between secret misconfiguration and attack; alert on rate-of-change, not absolute count.
  • Malformed body (406/400): previously 500; make sure this class of error is counted separately from genuine 5xx. A sustained 5xx-rate alert on garbage input is noise that trains people to ignore the page.
  • Unmatched parser returns null: if we map that to a 404 without an alert, events are dropped invisibly. Emit a metric and a page.
  • Duplicate deliveries: providers retry; the component gives us no idempotency key, so dedupe must be ours.
  • No replay window: add timestamp tolerance and nonce storage explicitly.
  • Payload size / JSON depth limits, and clock skew on timestamp checks.

Ramp-up. One to two days for a Laravel developer to be productive: the abstraction is thin, and "learning the package" is mostly "learning webhooks correctly," which is a durable skill. The friction is nomenclature (RemoteEvent, RequestParserInterface) and the absent Laravel docs — mitigated by one in-repo reference example. Overall this remains a conditional adopt: inbound-only as a parsing and signature-verification layer behind our own adapter, proven on one provider via shadow mode; defer the outbound half until we have a delivery layer to justify it.

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.
likyly/sdk
tdt/input
coreation/php-shapefile
coreation/gisconverter
primix/widgets
primix/notifications
primix/actions
primix/tables
primix/details
primix/forms
primix/support
marcelgwerder/laravel-api-handler
firebrikdotcom/conveyor-laravel-broadcaster
kanata-php/conveyor-laravel-broadcaster
alish/short-message
sonrac/lumen-league-oauth2
pushery/sanctum-hardening-for-laravel
evanschleret/laravel-uuidable
yigit-serin/laravel-openvidu
adachsoft/command-executor-lib