SNS, SQS, and webhooks all solve the same surface-level problem: you have something that happened, and you need another system to know about it. But the similarity stops there. Each tool makes fundamentally different assumptions about who controls the consumer, where the data goes, and what "reliable delivery" actually means.
Choosing the wrong one creates work you didn't budget for. Using them together without understanding the boundaries creates systems that are hard to debug and harder to extend.
This post is a direct comparison — infrastructure characteristics, failure modes, and decision criteria — so you can make the call with confidence.
What Each One Actually Is
Amazon SNS (Simple Notification Service) is a pub/sub message bus. You publish a message to a topic; SNS fans it out to all subscribers. Subscribers can be SQS queues, Lambda functions, HTTP endpoints, mobile push targets, email addresses, or SMS. SNS is push-based: it delivers to every subscriber and does not wait for acknowledgment before moving on.
Amazon SQS (Simple Queue Service) is a durable message queue. Producers write messages to a queue; consumers poll the queue and process one message at a time (or in batches). SQS holds the message until a consumer explicitly deletes it after successful processing. It is the standard pattern for decoupling internal services within your own infrastructure.
Webhooks are HTTP callbacks. You register a URL with a provider; the provider sends an HTTP POST to that URL when an event occurs. Webhooks are fundamentally a push-to-external mechanism: the sender initiates the HTTP request to a URL they don't own, and the receiver is responsible for responding within a deadline.
These three are not alternatives to each other in a strict sense — they operate at different layers of a system:
| Characteristic | SNS | SQS | Webhooks |
|---|---|---|---|
| Delivery model | Push to all subscribers | Pull by consumers | Push to registered URL |
| Who owns the consumer? | You | You | Your customer or partner |
| Transport protocol | AWS-internal + HTTP/HTTPS | AWS-internal | Plain HTTPS |
| Consumer acknowledgment | No explicit ack | Delete-on-success | HTTP 2xx response |
| Durable storage | No (7-day dead-letter at best) | Yes (up to 14 days) | Depends on implementation |
| Cross-account / cross-org delivery | Via SQS subscription or HTTP | No (same account) | Yes — anyone with a URL |
| Built-in retry | Yes (3 retries default) | N/A — consumer controls | Varies by provider |
| Fan-out | Native (broadcast to all subscribers) | Manual (read once) | Manual (one URL per event) |
The Core Use-Case Divide
SNS: Internal fan-out to known subscribers
Use SNS when you have one event that multiple internal systems need to react to, and those systems are all inside your AWS account (or trusted partner accounts).
The canonical pattern is SNS + SQS fan-out: one SNS topic, multiple SQS queue subscriptions. Each downstream service gets its own queue and processes independently. One slow consumer does not block another.
order.created (SNS topic)
│
├── fulfillment-queue (SQS) ──► fulfillment service
├── analytics-queue (SQS) ──► data pipeline
└── email-queue (SQS) ──► notification serviceThis pattern excels at:
- ›Broadcasting one event to multiple internal consumers
- ›Isolating downstream failures (a bug in the analytics service doesn't affect fulfillment)
- ›Backpressure handling (each queue absorbs bursts independently)
It does not work for delivering events to your customers. SNS has no concept of a per-customer endpoint, no authentication delegation, no event history visible to the customer, and no replay API you can expose.
SQS: Internal work queues between your own services
Use SQS when you have a producer and a single consumer, and you need durable, ordered-ish, exactly-once processing with explicit acknowledgment.
SQS is the right tool for:
- ›Offloading slow work from a web request (resize this image, send this email)
- ›Rate-limiting expensive operations (process no more than 10 payments per second)
- ›Decoupling two services so either can be deployed independently
The failure mode unique to SQS is the visibility timeout problem: if your consumer takes longer to process a message than the visibility timeout (default 30 seconds), SQS makes the message visible again and another consumer picks it up. You process it twice. Design your consumers for idempotency regardless.
// Good: idempotent SQS message handler
func processMessage(ctx context.Context, orderID string) error {
// Use INSERT ... ON CONFLICT DO NOTHING to handle double-delivery
affected, err := db.ExecContext(ctx, `
INSERT INTO fulfillment_jobs (order_id, status)
VALUES ($1, 'queued')
ON CONFLICT (order_id) DO NOTHING
`, orderID)
if err != nil {
return err
}
rows, _ := affected.RowsAffected()
if rows == 0 {
// Already processed — safe to return nil and delete the message
return nil
}
return dispatchFulfillment(ctx, orderID)
}Webhooks: Delivering events across organizational boundaries
Webhooks are the right choice — and often the only realistic choice — when the consumer is outside your infrastructure. Your customer's backend. A partner's integration. A third-party SaaS tool.
SQS does not work here: your customer cannot poll your AWS SQS queue without you granting them AWS credentials, which is an authentication and trust nightmare at scale. SNS HTTP subscriptions come close, but they deliver raw SNS envelope JSON (not your payload), require your customer to confirm a subscription via a specific handshake, and send no authentication header your customer can verify.
Webhooks solve this cleanly: you send a signed HTTP POST to a URL the customer controls, they verify the signature with a shared secret, and they respond with a 2xx. Every step is standard HTTP. No AWS accounts, no SDKs, no envelope formats.
Where webhooks require investment is in the delivery infrastructure: retry logic, dead-letter queues, idempotency keys, event replay, signature rotation, and per-destination observability. For most teams, building that from scratch is a multi-sprint project. That's the gap that tools like GetHook fill — you get a production-grade delivery layer without writing the infrastructure yourself.
Failure Modes Side by Side
Understanding how each system fails is as important as knowing when to use it.
| Failure scenario | SNS | SQS | Webhooks |
|---|---|---|---|
| Consumer is down | Message delivered to other subscribers; this subscriber may miss it unless backed by SQS | Message stays in queue until consumer recovers | Retry with backoff; dead-letter after max attempts |
| Consumer is slow | SNS doesn't wait; other subscribers unaffected | Message stays visible until acked; visibility timeout causes re-delivery | Request times out; counted as a failure; retry scheduled |
| Payload is malformed | Delivered as-is; consumer crash loops | Message hits DLQ after maxReceiveCount | Provider or gateway receives 4xx; may or may not retry |
| Network partition | SNS retries up to 3 times then drops | SQS retains message indefinitely | Delivery gateway retries on schedule; DLQ if exhausted |
| Duplicate delivery | Possible (at-least-once) | Possible (at-least-once for standard queues) | Possible; use idempotency keys at consumer |
The key difference: SQS never drops a message — it holds it until someone deletes it. SNS and webhooks both have finite retry budgets, after which a message is either dead-lettered or gone.
The Hybrid Pattern: When You Need Both
The most common production architecture uses all three in combination:
External provider
│
▼
[Webhook ingest endpoint] ← verify signature, respond 200 immediately
│
▼
[Internal SQS queue] ← durable storage, decouples ingest from processing
│
▼
[Processing service] ← enriches event, applies business logic
│
▼
[SNS topic] ← fan-out to internal subscribers
│
├── [Analytics SQS queue]
├── [Fulfillment SQS queue]
└── [Outbound webhook delivery] ← delivers to your customers via webhookThis pattern is common in platforms that consume upstream webhooks (from Stripe, Shopify, etc.) and re-deliver processed events to their own customers. You receive via webhook, durably queue with SQS, fan out with SNS, and deliver outbound with webhooks again.
Each tool is doing what it's best at:
- ›Webhook ingest: accepts external push with HTTP authentication
- ›SQS: durable buffer, decouples the receiving HTTP handler from slow processing
- ›SNS: broadcasts the processed event to multiple internal consumers
- ›Outbound webhook: delivers to customer URLs with retry, replay, and signing
Decision Guide
Ask yourself these questions in order:
1. Is the consumer outside your organization? Yes → webhook. Neither SQS nor SNS is designed for cross-org delivery.
2. Do you need multiple independent consumers of the same event? Yes → SNS (optionally with SQS subscriptions per consumer). SQS reads are destructive; only one consumer gets each message.
3. Do you need durable storage with explicit acknowledgment and no message loss? Yes → SQS. SNS doesn't give consumers an explicit ack mechanism; webhooks lose the message after the retry budget is exhausted.
4. Do you need customers to have a self-service view of their event history, replay capability, or delivery status? Yes → webhooks with a proper delivery gateway. You cannot expose SQS or SNS internals to customers in a sensible way.
5. Are you delivering to a Lambda or another AWS service in your own account? SNS or SQS, depending on whether you need fan-out or sequential processing. Webhooks add unnecessary network hops and HTTP overhead here.
What This Looks Like in Practice
A team building a payment platform might use all three:
- ›Stripe sends webhooks to the platform's ingest layer (webhook, because Stripe is external)
- ›The ingest layer queues events to SQS (durable buffer before processing)
- ›After processing, an SNS topic notifies internal services (fraud detection, accounting, fulfillment)
- ›The fulfillment service emits outbound webhooks to the platform's customers (webhook, because customers are external)
Each handoff uses the appropriate tool. The system is easier to reason about because each boundary is explicit.
If you're building the outbound webhook layer in this stack, GetHook handles the delivery infrastructure — retry logic, dead-letter queues, signing, replay, and per-destination observability — so your team can focus on the business logic rather than the delivery plumbing.