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

Laravel Aws Webhooks Laravel Package

renoki-co/laravel-aws-webhooks

Laravel AWS Webhooks helps you receive and process AWS SNS webhook notifications in Laravel. It verifies message signatures, handles subscription confirmations, and routes incoming events to your own handlers for a secure, simple AWS webhook integration.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is. renoki-co/laravel-aws-webhooks gives you a Laravel endpoint that receives AWS SNS HTTP(S) deliveries, verifies the SNS message signature, and hands the payload to your application. It is glue, not a framework — the value is the signature verification and subscription handshake you would otherwise hand-roll.

Version 5.2.0 in one line. No new features or API changes; this is a support-matrix release (Laravel 10 support added, PHP 8.2 added, PHP 8.0 and Laravel 8.x dropped). Treat it as a maintenance upgrade, not a feature upgrade.

Minimum steps

composer require renoki-co/laravel-aws-webhooks   # resolves to ^5.2 on Laravel 9/10
php artisan vendor:publish --tag="aws-webhooks-config"

Then:

  1. Open config/aws-webhooks.php — this is the first file to read. It holds the AWS region, credentials source, the expected topic ARN(s), and the route/middleware the package registers.
  2. Confirm the package's route is reachable. By default it registers an endpoint for you; check php artisan route:list | grep -i webhook before writing any controller of your own.
  3. Put real values in .env (AWS_DEFAULT_REGION, key/secret or an IAM role) — the package needs AWS API access to fetch the SNS signing certificate and to confirm subscriptions.
  4. In the SNS console, subscribe your https://your-app.test/... endpoint to a topic, then publish a test message.

First real use case. Wire one SNS topic to one queued job:

// app/Providers/EventServiceProvider.php
protected $listen = [
    \RenokiCo\LaravelAwsWebhooks\Events\AwsWebhookReceived::class => [
        \App\Listeners\HandleAwsWebhook::class,
    ],
];
class HandleAwsWebhook
{
    public function handle($event): void
    {
        ProcessSnsMessage::dispatch($event->payload)->onQueue('aws');
    }
}

Start here rather than writing a controller: the package already did the parsing and verification, and the listener keeps your HTTP path trivially fast.

Verify the exact event class name and config keys against the published config and README for your tag — this package has renamed internals across major versions, and 5.2.0 is not one of the releases that changed them.

Implementation Patterns

Let the package own the route. The most common mistake is reimplementing the endpoint. Use the registered route with the package's validating middleware, and only reach for a custom controller when you need per-topic routing (e.g. /aws/sns/billing vs /aws/sns/ops). If you do go custom, keep the validation middleware attached — never withoutMiddleware() it outside tests.

Parse Message, not the envelope. SNS wraps everything. The outer JSON is transport (Type, MessageId, TopicArn, Signature, SigningCertURL); the actual domain event is a JSON string inside Message. Model it explicitly:

final class SnsEnvelope
{
    public static function from(array $payload): self { /* ... */ }

    public function inner(): array
    {
        return json_decode($this->payload['Message'] ?? '{}', true) ?: [];
    }

    public function isSesBounce(): bool
    {
        return str_starts_with($this->inner()['notificationType'] ?? '', 'Bounce');
    }
}

Then branch in a single handler: SES bounces/complaints, CloudWatch alarm state changes, EventBridge forwarded events, and raw Notification messages all arrive through the same door.

Branch on Type before anything else. SubscriptionConfirmation, UnsubscribeConfirmation, and Notification require different handling. Subscription confirmation is a handshake — the package handles it, but you should log it, because a silently failed confirmation is the #1 reason "the webhook works in staging but not production."

Assume at-least-once delivery. SNS retries on any non-2xx and can duplicate on success. Deduplicate on MessageId:

if (! Cache::add('sns:'.$envelope->messageId, true, now()->addDay())) {
    return response()->noContent(); // already processed
}

A unique index on a sns_messages table is the durable version of this; the cache version is fine for low volume but loses the guard on cache:clear.

Acknowledge fast, work later. The HTTP handler should do verification → dedupe check → dispatch → 200. Anything slower than a second or two risks SNS timeouts and duplicate retries. Give the queue job $tries, $backoff, and a failed() that alerts — a failed job is invisible to SNS, which believes delivery succeeded.

Integration tips

  • Exclude the endpoint from CSRF: put it in routes/api.php, or add the path to VerifyCsrfToken::$except. This bites everyone once.
  • If the package's route isn't in your api group by default, set the middleware in config/aws-webhooks.php rather than duplicating routes.
  • For multiple topics, register one route per topic with a distinct ARN allow-list; do not accept "any topic in the account."
  • In tests, fake at the HTTP layer with Http::fake() for the certificate fetch and post a fixture payload to the route. Add a separate test that asserts a tampered Signature yields a 4xx — that assertion is the entire point of the dependency.

Gotchas and Tips

5.2.0 is breaking for some consumers. Composer will resolve it automatically on Laravel 9/10, but:

You are on Action
Laravel 10, PHP 8.1/8.2/8.3 Safe upgrade, no code changes
Laravel 9, PHP 8.1+ Safe upgrade
Laravel 9, PHP 8.0 Blocked — pin renoki-co/laravel-aws-webhooks:^5.1 until you bump PHP
Laravel 8 Blocked — stay on ^5.1; 5.2.0 will not install

Check composer.json require.php before running composer update, and confirm your CI matrix actually tests 8.2 if you're claiming support for it. Also confirm your Laravel ^10.0 constraint is real and not ^9.0 with an allow-list.

Signature verification is fussy about the raw body. Verify against the bytes as received. If you log or re-encode the payload before validation, or if a middleware mutates the request body, verification fails with a valid-looking signature. Don't add request-body-rewriting middleware ahead of the endpoint.

Certificate fetching is an SSRF surface. The SigningCertURL is attacker-controlled on a forged request. The package constrains it, but if you extend or replace the validator, enforce: https only, host ends in .amazonaws.com, no redirects, and a short HTTP timeout with a cache. Never proxy through a user-supplied URL.

Clock skew and expiry. Signature validation is time-sensitive; containers with drifted clocks fail intermittently. Keep NTP on, and if you see sporadic 4xx on a payload that replays fine locally, check the timestamp before you check the code.

SNS payloads cap at 256 KB, so the body fits any sane PHP config — but SES bounce payloads can be large enough that logging them verbatim will bloat your log storage. Log MessageId, Type, TopicArn, and a truncated preview; store full payloads in a table or object storage only when debugging.

Debugging workflow, in order:

  1. tail -f storage/logs/laravel.log while publishing a test message from the SNS console.
  2. No log line at all → routing/CSRF/TLS problem, not a package problem. Check route:list, then the SNS "Delivery status" / CloudWatch NumberOfNotificationsFailed metrics.
  3. Log line but 4xx → verification. Compare SigningCertURL host, TopicArn against your allow-list, and the timestamp.
  4. 200 but no job → the listener isn't registered or the event name changed; php artisan event:list.
  5. Duplicate processing → you're returning a non-2xx after the side effect, or you have no idempotency key.

Extension points worth knowing: the package's events (listen rather than override), the publishable config for ARN allow-listing and route middleware, and the middleware itself — it's the right seam if you need custom topic routing or extra header checks. Prefer listening to events over subclassing the internals; the internals have moved between majors.

Local development. SNS cannot reach localhost. Use a tunnel (ngrok, expose) and re-subscribe the tunnel URL each time it changes — SNS does not follow redirects and stale subscriptions fail silently. For deterministic local tests, skip SNS entirely and post a captured fixture to the route with validation stubbed in a test-only service provider.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels