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 Sns Listener Laravel Package

sinemacula/laravel-aws-sns-listener

A Laravel package for receiving and handling AWS SNS notifications, with support for routing messages to listeners, managing subscriptions, and verifying signatures so you can integrate push notifications and event-driven workflows into your app.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer; the service provider is auto-discovered, so there is nothing to register manually.

composer require sinemacula/laravel-aws-sns-listener
php artisan vendor:publish --provider="SineMacula\Aws\Sns\ServiceProvider"

Publish the config first and read it before writing any code — it is where the route path, middleware stack, and expected topic ARNs live. Check the package README for the exact --tag value, since tags are not listed in the release notes.

// config/aws-sns-listener.php (shape, confirm keys against the published file)
return [
    'route' => [
        'uri'        => 'webhooks/aws/sns',
        'middleware' => ['api'], // must not include CSRF verification
    ],
    'topics' => [
        // ARNs you are willing to accept messages from
    ],
];

Then:

  1. Expose the endpoint. The package ships the route, controller, and signature-validating middleware. Your job is to make POST /webhooks/aws/sns publicly reachable over HTTPS (a tunnel such as ngrok or Expose is fine in development).
  2. Subscribe the endpoint in SNS. Create/confirm the HTTPS subscription on your topic. The subscription confirmation request is handled by the package — you do not write code for it. If the subscription stays PendingConfirmation, your endpoint is not returning the confirmation payload correctly (see Gotchas).
  3. Write your first listener. The fastest first use case is SES feedback: subscribe an SES configuration set to an SNS topic, point the topic at your app, and react to bounces/complaints by suppressing the address. Start with the generic notification event to see the payload shape, then switch to the typed event.
namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use SineMacula\Aws\Sns\Events\NotificationReceived; // confirm exact FQCN in the repo

class LogSnsNotification implements ShouldQueue
{
    public function handle(NotificationReceived $event): void
    {
        logger()->info('sns.received', [
            'message_id' => $event->message->getMessageId(),
            'topic'      => $event->message->getTopicArn(),
        ]);
    }
}

Where to look first: src/ mirrors the whole surface — the service provider (route/config wiring), the middleware (signature validation), the message factory (payload → typed entity), and the Events/ directory (what you actually listen to). The typed entities for SES, S3, and CloudWatch tell you exactly which accessors you get in a listener.

Implementation Patterns

Listen to typed events, not raw JSON. The package maps SNS payloads into typed message entities and dispatches a distinct Laravel event per provider. Type-hint the event in handle() and use the entity's accessors rather than json_decode-ing the request yourself.

public function handle(SesNotificationReceived $event): void
{
    $notification = $event->message->getNotification();

    if ($notification->isBounce()) {
        Suppression::firstOrCreate(['email' => $notification->getBouncedRecipient()]);
    }
}

Keep listeners thin and queued. Implement ShouldQueue on every listener so the webhook request returns 2xx immediately and processing happens on your queue worker. This decouples SNS's retry behaviour from your processing time and gives you Laravel's retry/backoff/DLQ semantics for free.

Use one generic listener as a catch-all. Typed guards only fire for recognised notification types. Register a listener on the generic notification event to log or park anything unrecognised — otherwise a new SNS payload shape silently disappears.

public function handle(NotificationReceived $event): void
{
    if (! $this->isHandled($event)) {
        Log::warning('sns.unhandled_type', ['type' => $event->message->getType()]);
    }
}

Fan out via Laravel's event system, not the controller. Because each notification becomes a normal Laravel event, you get multiple independent listeners, Event::fake() in tests, and event discovery. Do not add business logic in the controller or middleware.

Make handlers idempotent. SNS is at-least-once. The package's v2.0.1 "deduplicate typed notification guards" change is about internal event dispatch, not message-level deduplication — assume you must dedupe yourself. A cheap pattern:

$key = 'sns:'.$event->message->getMessageId();

if (! Cache::add($key, true, now()->addDays(7))) {
    return; // already processed
}

Validate the topic allowlist in config, not code. Populate the expected-topic list in the published config so the package can reject messages from topics you did not subscribe. Multi-tenant or multi-environment setups should drive this from env vars rather than hardcoding ARNs.

Test with Event::fake() for your handlers and real HTTP posts for the integration. Unit-test listeners by faking the event and asserting side effects. Separately, keep one feature test that POSTs a captured SNS envelope to the route with signature checking disabled in the test environment, so you catch middleware/route regressions.

Local iteration loop. Capture a real payload from CloudWatch Logs once, save it as a fixture, and replay it via Http::post() in a test or a small artisan command. This is far faster than re-triggering SNS and avoids needing a public tunnel for every change.

Gotchas and Tips

  • CSRF will break the webhook. If the route group includes web middleware, VerifyCsrfToken will 419 every SNS request. Use the api group or add the endpoint to $except. The package ships middleware, but it does not remove CSRF for you.
  • Signature verification needs the raw body. Any middleware that re-encodes or mutates the request before validation will invalidate the signature. Keep the route's middleware stack minimal and verify ordering if you add your own.
  • Signature validation makes outbound HTTPS calls to fetch AWS signing certificates. In an egress-restricted or air-gapped environment this fails closed and every message is rejected. Allowlist the SNS certificate host, and be aware that first-message latency includes that fetch.
  • Subscription confirmation is a chicken-and-egg problem. Your app must be publicly reachable before the subscription is created, and must answer the SubscriptionConfirmation POST quickly (SNS times out). If confirmation hangs, check that the route is reachable, that CSRF is off, and that no auth middleware blocks unauthenticated AWS traffic.
  • A non-2xx response triggers SNS retries with backoff. Return 200 as soon as the message is validated and dispatched/queued. If you process synchronously and throw, SNS will redeliver — which, combined with no idempotency, means duplicate side effects.
  • Config is cached in production. After changing topic ARNs or the route path in .env, run php artisan config:cache (and config:clear locally) or your changes will appear to do nothing. This is the most common "the package is broken" report.
  • Queue workers, not the web request, are your scaling knob. With at-least-once delivery plus SNS bursts, watch queue backlog and visibility timeouts. Use SQS/Redis-backed queues for high volume rather than the database driver, and configure retry/failed() handling — anything your listener drops goes to failed_jobs, not back to SNS.
  • Log the MessageId, topic ARN, and SNS type on every message. These are the only fields that let you correlate a support ticket with an SNS delivery and dedupe store entry. Without them, debugging is guesswork.
  • Custom extensions are the regression surface for v2.0.1. If you subclass the message factory, add your own typed guards, or decorate the middleware, re-test after upgrading from 2.0.0 — the release deduplicated typed notification guards internally, and patch-level refactors of dispatch logic are exactly where custom extensions break. Run composer audit too; the release bumped dependencies for security reasons without naming the CVEs.
  • Confirm the support matrix before adopting. The dependency set (Carbon 3.8.x) implies a modern PHP baseline; verify your Laravel major and PHP version against the package's composer.json rather than assuming. Pin the version and let Dependabot-style patch upgrades flow through your normal audit cycle.
  • Extension points to prefer: the published config (topics, route, middleware), the message factory (custom mappings), and the events layer (your listeners). The AGENTS.md explicitly frames this as an integration layer, not an AWS SDK wrapper — do not expect it to provision topics, manage subscriptions, or implement business logic for you.
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.
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
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console