SNS subscription stuck in PendingConfirmation?

You subscribe an HTTP or HTTPS endpoint to an SNS topic, and the console shows the subscription as PendingConfirmation — forever. No notifications arrive, because SNS sends nothing to an endpoint until the subscription is confirmed. The confirmation is itself a webhook: SNS POSTs a SubscriptionConfirmation message to your endpoint containing a SubscribeURL, and someone — you or your code — has to issue an HTTP GET to that URL (or call ConfirmSubscription with the Token). Until that happens, the subscription is pending. Here's why the confirmation message so often vanishes, and the zero-code way out.

The zero-code fix: capture it, click it

If you just need the subscription confirmed (or you want to see what SNS is actually sending before your real endpoint handles it), point the subscription at a capture URL:

$ curl https://hookden.pages.dev/new

Subscribe that https://hookden.pages.dev/h/… URL to your topic (protocol HTTPS). The SubscriptionConfirmation POST appears in your bin within seconds — full headers and body. Copy the SubscribeURL from the captured JSON and open it in your browser, or paste it into the console's Confirm subscription dialog. Status flips to a real SubscriptionArn, and every message you publish to the topic now lands in the bin where you can inspect it. When you're done reverse-engineering the payload shape, re-subscribe your real endpoint.

If your real endpoint is on localhost or a private network, you can keep the bin subscribed and relay deliveries down to your machine — see webhooks to localhost without a tunnel.

Cause 1: your JSON parser never saw it — the text/plain trap

The single most common reason the confirmation "never arrived" is that it arrived and your framework dropped it. SNS sends every message — including the confirmation — with Content-Type: text/plain; charset=UTF-8 by default, even though the body is JSON. express.json(), Rails' JSON params, FastAPI's model binding — none of them parse a text/plain body, so your handler sees an empty object and returns an error (or your route 404s on the missing fields), and the confirmation is lost.

Fixes, any one of which works:

A capture URL shows this instantly: the bin displays the exact Content-Type SNS sent, next to the JSON body your parser refused to parse.

Cause 2: SNS gave up quickly — 3 retries by default

The confirmation message follows the subscription's HTTP/S delivery policy, and the default is small: numRetries: 3, with 20-second delay targets. Only 5xx and 429 responses are retried — any other non-2xx (a 404 route, a 401 from auth middleware, a redirect you don't follow) is treated as a permanent failure and the message is discarded. The whole HTTP/S retry window is hard-capped at 3,600 seconds no matter what policy you configure.

So if your endpoint wasn't ready in the first minute or two, the confirmation is simply gone. Get a fresh one: select the pending subscription in the SNS console and click Request confirmation — SNS re-sends the SubscriptionConfirmation message. (Unconfirmed subscriptions are removed automatically after about 3 days, so a stale pending row will eventually clean itself up.)

Cause 3: SNS can't reach the endpoint at all

To see SNS's side of the story, enable delivery status logging on the topic and read the providerResponse attribute in the CloudWatch failure logs — it names the exact error (timeout, TLS, connection refused).

Know what you're looking at: the message anatomy

Every SNS POST carries identifying headers, so you can route without parsing the body:

The confirmation body contains Type, MessageId, Token, TopicArn, Message, SubscribeURL, Timestamp, SignatureVersion, Signature, and SigningCertURL. Two notes worth knowing:

Quick checklist

Related: test webhook retry behavior, webhooks to localhost without a tunnel, and other verification handshakes (Meta's GET challenge).

No signup needed. Or from your terminal: curl https://hookden.pages.dev/new

← All guides · Docs · Hookden vs webhook.site