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.
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.
text/plain trapThe 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:
JSON.parse it yourself when the
x-amz-sns-message-type header is present.express.json({ type: () => true })
on that route."requestPolicy": {"headerContentType": "application/json"}.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.
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.)
InvalidParameter: Unreachable Endpoint.SSLPeerUnverifiedException in HttpClient.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).
Every SNS POST carries identifying headers, so you can route without parsing the body:
x-amz-sns-message-type — SubscriptionConfirmation,
Notification, or UnsubscribeConfirmationx-amz-sns-topic-arn — which topic sent itx-amz-sns-message-id — stable across retries of the same messageThe confirmation body contains Type, MessageId,
Token, TopicArn, Message, SubscribeURL,
Timestamp, SignatureVersion, Signature, and
SigningCertURL. Two notes worth knowing:
SignatureVersion 1
(SHA1withRSA); you can switch a topic to version 2 (SHA256withRSA) with
SetTopicAttributes. If you verify, always check the
SigningCertURL is an https:// URL on an
amazonaws.com domain before fetching the certificate.Notification messages — it
strips the JSON envelope so your endpoint gets the published payload as-is. The
confirmation handshake still arrives as the JSON envelope above.text/plain by default)SubscribeURL (your code, your browser, or the console's
Confirm subscription dialog)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