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

Receiver Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish.

composer install
php artisan vendor:publish --tag=receiver-config

Then add a provider entry to config/services.php. v1.0.0's README has an explicit, copy-pasteable block for every built-in provider — start there rather than guessing key names.

// config/services.php
'shopify' => ['secret' => env('SHOPIFY_WEBHOOK_SECRET')],

'twilio'  => ['token' => env('TWILIO_AUTH_TOKEN')],

'mailchimp' => ['secret' => env('MAILCHIMP_WEBHOOK_SECRET')],

'sendgrid' => [
    'public_key' => env('SENDGRID_VERIFICATION_KEY'), // opt-in ECDSA
],

'paddle' => [
    'secret'          => env('PADDLE_WEBHOOK_SECRET'),
    'previous_secret' => env('PADDLE_WEBHOOK_SECRET_OLD'), // key rotation
],

First use case: log before you build. Point one webhook at a throwaway route and dump the verified event once:

Route::post('/webhooks/{provider}', function (string $provider) {
    $receiver = Receiver::driver($provider);

    $receiver->validate(request());          // 4xx on bad signature, before handlers run
    logger()->info('event', ['event' => $receiver->getEvent()]);

    return $receiver->receive(request());    // dispatches handler(s)
});

That single log line tells you the exact event name string and payload shape — which is the only thing you actually need to name your handler class correctly.

Where to look first, in order:

  1. The README provider table + per-provider config examples (fully rewritten in 1.0.0).
  2. The handler naming table — this determines which class gets invoked for a given event name.
  3. The middleware/queue references in the README before you wire up your own queueing.

Implementation Patterns

Verify, then dispatch

Keep the controller to three lines: resolve the driver, verify, receive. Any normalisation of the payload should happen inside your handler, never before verification — HMAC providers sign the raw bytes.

One request → N handler calls (new in 1.0.0)

getEvent() may now return an array of ['event_name' => $eventData] pairs, and the receiver dispatches a separate handler per entry. For batched providers like SendGrid, one HTTP request fans out into many handler invocations. Practically this means:

  • Treat every handler as if it could be called alone, in any order, more than once.
  • Key idempotency on the provider's event ID (payload['id'], payload['event_id'], etc.) in a cache lock or a processed_events table.
  • Do not assume "I got one request, so I got one logical event."

Keep handlers thin

class PaymentAuthorizationCreated implements Handler
{
    public function handle(array $payload): void
    {
        ProcessPaddleEvent::dispatch($payload)->onQueue('webhooks');
    }
}

The receiver only has to hand off; anything slower than a few hundred milliseconds belongs in a queued job. Your HTTP response should be a fast 200 so the provider doesn't retry.

Handler naming is mechanical — verify it with a test

Event names are lowercased and converted to StudlyCase before class resolution. PAYMENT.AUTHORIZATION.CREATED → PaymentAuthorizationCreated; push → Push. Because it's deterministic, write one cheap test that asserts class_exists() for every handler you ship:

foreach (['payment.authorization.created', 'subscription.created'] as $event) {
    $this->assertTrue(class_exists(Str::studly(str_replace(['.', '-'], '_', $event))));
}

That test catches 90% of "handler never fires" reports before deploy.

Custom / community providers

php artisan receiver:make MyProvider --provider scaffolds the provider class and a companion ServiceProvider wired for Laravel package auto-discovery, so you can publish it as a standalone Composer package. Inside that package's composer.json, register it under extra.laravel.providers — nothing else.

Layered verification (Postmark)

Postmark now supports auth (HTTP Basic), headers (custom headers), and ips (published Postmark IP ranges), and you can combine them. Day-to-day rule: use auth as the primary, add ips only if you can resolve real client IPs behind your proxy, and use headers when Postmark can't send credentials to your endpoint.

Paddle key rotation

Configure both the current and previous secret, let the provider accept either, rotate in the dashboard, then delete previous_secret after your window. Do not rotate without the overlap.

Gotchas and Tips

  • Raw body or nothing. HMAC-SHA256 (Shopify, Paddle) and HMAC-SHA1 (Twilio) sign the request body bytes. Middleware that parses and re-encodes JSON, or anything that mutates php://input, will produce valid-looking-but-failing signatures.
  • CSRF. Webhook routes must be outside VerifyCsrfToken (put them in the api group or add them to $except). A 419 is invisible to the provider.
  • config:cache after every services.php edit. A stale cache is the single most common cause of "signature invalid" on a config that is demonstrably correct. Clear it first, debug second.
  • The GitHub trailing-underscore bug is fixed. Payloads without an action field (e.g. push, fork) no longer emit push_. If you built workarounds around the old string, remove them and re-check your handler class names against the 1.0.0 naming table. Upgrade notes call it non-breaking, but it does change the resolved name.
  • ALL_CAPS event names resolve correctly now. PayPal-style PAYMENT.AUTHORIZATION.CREATED previously could mis-resolve; anything you named to compensate for that can be deleted.
  • Multi-event partial failure. If one handler throws, assume the whole request fails and the provider retries the whole batch. Make handlers idempotent and scope each side effect to its own event.
  • SendGrid ECDSA is opt-in. Leaving it off means unverified requests hit your handlers. Turn it on unless another trusted boundary sits in front — it needs the account's verification key and the openssl extension.
  • Mailchimp's secret rides in the URL. It will land in access logs, Referer headers, and error trackers. Redact the query string at your reverse proxy and rotate via config, not a code change.
  • Postmark ips behind a load balancer. request()->ip() will be your proxy. Either configure TrustProxies correctly or combine ips with auth so a misconfigured proxy doesn't silently block every webhook.
  • Don't double-register the generated ServiceProvider. Auto-discovery plus a manual entry in config/app.php gives you duplicate registration; pick one.
  • Debugging flow that actually works. Temporarily log request()->headers->all() plus the raw body before verification. A wrong secret and a wrong driver name produce identical silent 4xx responses — the log distinguishes them in one deploy.
  • Version matrix. CI covers Laravel 10–13 × PHP 8.2–8.4. If you're contributing a provider upstream, composer run lint:check and composer run analyse (PHPStan level 5 + Pint) are the same gates CI applies.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons