Notes · Sep 6, 2026

# SNS to SQS: why your Lambda calls JSON.parse twice

A message published to SNS and delivered through SQS reaches your function as JSON inside a string inside a string. Nobody designed that; three services each did the reasonable thing.

Unstringify a packed value — An SNS notification: the tree, then Unpack on the Tools row opens the JSON packed inside Message, in place — the text is rewritten and undo puts the string back. · 8 s

## Three documents in one record

The Lambda event is JSON. Its first record’s body is a string, and the string is the SNS envelope — Type, MessageId, TopicArn, Timestamp — as JSON. The envelope’s Message is a string too, and that string is the event your producer published. This producer went one further and serialised a field of its own, so the sample holds 3 levels of JSON inside a string in 18 lines. Read it in a log viewer and every quote past the first level is a backslash.

```
{
  "Records": [
    {
      "messageId": "2b1a5a2e-7c4f-4a58-9f6c-1f4c0d1c1234",
      "receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a…",
      "body": "{\"Type\":\"Notification\",\"MessageId\":\"9d4e7d1a-3b2c-4f5e-8a9b-0c1d2e3f4a5b\",\"TopicArn\":\"arn:aws:sns:eu-west-1:123456789012:checkout-events\",\"Message\":\"{\\\"event\\\":\\\"order.paid\\\",\\\"order_id\\\":\\\"ord_8f3aa19c4d\\\",\\\"amount\\\":17054,\\\"currency\\\":\\\"eur\\\",\\\"metadata\\\":\\\"{\\\\\\\"campaign\\\\\\\":\\\\\\\"spring-2026\\\\\\\",\\\\\\\"attempt\\\\\\\":2}\\\"}\",\"Timestamp\":\"2026-04-21T14:32:08.412Z\",\"SignatureVersion\":\"1\",\"MessageAttributes\":{}}",
      "attributes": {
…
```

## Where each layer comes from

SNS wraps what you publish in its envelope and serialises the envelope, because the envelope has to carry the message to any kind of subscriber. SQS stores a message body as text, because a queue does not read what it carries. Lambda hands you the SQS record as it is. Each hop is correct on its own; the parse-twice is the sum of them.

Two things catch consumers written for this shape. Turning on raw message delivery on the subscription removes the SNS envelope, so a consumer that parsed twice now fails on a body that is already the event. And the first message a new subscription receives is a SubscriptionConfirmation, whose Message is a sentence, not JSON — the second parse throws on the one message that was never meant to be parsed.

## Opening it in one click

Pasted, the record shows Unpack on the one packed value at the top — the body — and Unpack keeps going: the envelope, the event inside it, the field inside that, each opened in place and named by the parser that opened it, up to 8 levels. The tree reads as one document with the envelope’s MessageId beside the event’s order_id. Pack puts the exact bytes back, so what you copy out is what the queue delivered.

Over a log of these — one SQS record per line — the same value opens in the panel beside the row, and the line stays the string the log wrote.

## Upstream

If only queues subscribe to the topic, raw message delivery is the fix at the source: one layer fewer, one parse fewer, and the envelope’s fields you still need — MessageId for deduplication — arrive as SQS message attributes instead. If a function subscribes directly, keep the envelope and parse Message once, after checking Type. Either way, the next time a record lands in a log with its backslashes, the page opens it as it was sent.

## Try it

## Try it on the file that broke your afternoon.

free while in alpha · no signup

---

Canonical: https://unstringify.com/notes/sns-to-sqs-json-parse-twice
