Skip to content
Linuxbeast
  • Home
  • Today in Tech
  • Who is Hiring?
  • About Me
  • Work With Me
  • Tools
    • DevOps Onboarding
    • AWS VPC Subnet Planner
    • Tag Network
    • Pag-IBIG Housing Loan Calculator
  • Contact
How to Set Up Cross-Account SNS to SQS Subscription in AWS

How to Set Up Cross-Account SNS to SQS Subscription in AWS

March 9, 2026August 5, 2025 by Linuxbeast
6 min read

SNS to SQS within the same account is simple. You create a topic, create a queue, and subscribe. Done. But a cross-account SNS to SQS subscription needs more work. When the topic lives in one account and the queue lives in another, you have to set up resource policies on both sides.

This guide covers the IAM policies, CloudFormation resources, and common gotchas for this setup. If you’ve done other cross-account work before — like setting up AssumeRole or accessing Secrets Manager across accounts — this follows a similar pattern.

How Cross-Account SNS to SQS Works

Two AWS accounts are involved in this setup:

  • Account A (111111111111) — owns the SNS topic that publishes messages
  • Account B (222222222222) — owns the SQS queue that receives messages, plus an optional Lambda function to process them

The message flow is straightforward: SNS topic (Account A) → SQS queue (Account B) → Lambda (Account B).

To make this work, you need three things:

  1. An SNS topic access policy in Account A allowing Account B to subscribe
  2. An SQS queue policy in Account B allowing the SNS topic to send messages
  3. An SNS subscription created from Account B connecting the two

Prerequisites

  • Access to both AWS accounts with SNS, SQS, and IAM permissions
  • AWS CLI set up for both accounts — see How to Install AWS CLI v2 on Ubuntu 22.04 if needed
  • Basic knowledge of IAM resource policies and CloudFormation

Step 1: Configure the SNS Topic Policy in Account A

First, Account A needs to allow Account B to subscribe to its SNS topic. Without this policy, subscription attempts from Account B will fail with an authorization error.

Add this statement to the SNS topic’s access policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCrossAccountSubscription",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::222222222222:root"
      },
      "Action": "sns:Subscribe",
      "Resource": "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
    }
  ]
}

Replace 222222222222 with Account B’s actual ID. Also update the topic ARN to match your region and topic name.

For tighter security, you can restrict this to SQS protocol only. Just add a condition like this:

{
  "Sid": "AllowCrossAccountSQSSubscription",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::222222222222:root"
  },
  "Action": "sns:Subscribe",
  "Resource": "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name",
  "Condition": {
    "StringEquals": {
      "sns:Protocol": "sqs"
    }
  }
}

The Double Permission Requirement

Here’s something that catches people off guard. Even though Account A allows the subscription in the topic policy, that’s only half the picture. The IAM user or role in Account B also needs sns:Subscribe in their own IAM policy. Both sides have to agree before it works.

CloudFormation Version

In CloudFormation, the topic policy looks like this:

SNSTopicPolicy:
  Type: AWS::SNS::TopicPolicy
  Properties:
    Topics:
      - !Ref MyTopic
    PolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Sid: AllowCrossAccountSubscription
          Effect: Allow
          Principal:
            AWS: "arn:aws:iam::222222222222:root"
          Action: "sns:Subscribe"
          Resource: !Ref MyTopic

Step 2: Configure the SQS Queue Policy in Account B

Next, Account B’s SQS queue needs a policy that allows the SNS service to deliver messages. Without this, SNS drops the messages silently. You won’t get a useful error in most cases.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowSNSSendMessage",
      "Effect": "Allow",
      "Principal": {
        "Service": "sns.amazonaws.com"
      },
      "Action": "sqs:SendMessage",
      "Resource": "arn:aws:sqs:ap-southeast-1:222222222222:your-queue-name",
      "Condition": {
        "ArnEquals": {
          "aws:SourceArn": "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
        }
      }
    }
  ]
}

The Condition block is critical here. It restricts which SNS topic can send to this queue. Without it, any SNS topic from any account could push messages to your queue.

CloudFormation Version

SQSQueuePolicy:
  Type: AWS::SQS::QueuePolicy
  Properties:
    Queues:
      - !Ref MyQueue
    PolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Sid: AllowSNSSendMessage
          Effect: Allow
          Principal:
            Service: sns.amazonaws.com
          Action: "sqs:SendMessage"
          Resource: !GetAtt MyQueue.Arn
          Condition:
            ArnEquals:
              aws:SourceArn: "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"

Step 3: Create the Cross-Account SNS to SQS Subscription

Now that both policies are in place, you can create the subscription. Do this from Account B’s side. It points to Account A’s SNS topic ARN and uses Account B’s SQS queue as the endpoint.

Using CloudFormation

Deploy this resource in Account B’s stack:

CrossAccountSubscription:
  Type: AWS::SNS::Subscription
  Properties:
    TopicArn: "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
    Protocol: sqs
    Endpoint: !GetAtt MyQueue.Arn
    RawMessageDelivery: true

I recommend setting RawMessageDelivery to true. Without it, SNS wraps your message in its own JSON envelope. As a result, your consumer has to parse extra metadata just to reach the actual payload. With raw delivery, the message body arrives exactly as published.

Using AWS CLI

Alternatively, you can use the CLI to test before writing CloudFormation. Run this with Account B’s credentials:

aws sns subscribe \
  --topic-arn "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name" \
  --protocol sqs \
  --notification-endpoint "arn:aws:sqs:ap-southeast-1:222222222222:your-queue-name" \
  --attributes '{"RawMessageDelivery":"true"}'

A successful response returns a subscription ARN. However, if you get an authorization error, check both the SNS topic policy in Account A and the IAM permissions in Account B.

Step 4: Test the Setup

To verify, publish a test message from Account A:

aws sns publish \
  --topic-arn "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name" \
  --message '{"test": "cross-account message"}'

Then poll the SQS queue from Account B:

aws sqs receive-message \
  --queue-url "https://sqs.ap-southeast-1.amazonaws.com/222222222222/your-queue-name" \
  --max-number-of-messages 1

If you see the message, everything is working. If the queue is empty, check these in order:

  1. Subscription status — open the SNS console in Account A and confirm it shows “Confirmed,” not “Pending confirmation”
  2. SQS queue policy — verify the aws:SourceArn condition matches the SNS topic ARN exactly
  3. Region — both resources must be in the same region for standard topics

Common Pitfalls

Manual Subscription Conflicting with CloudFormation

You’ll probably run into this at some point. If you create a subscription manually through the console to test, and then later deploy the same one through CloudFormation, you get this error:

Subscription already exists with different attributes

To fix it, delete the manual subscription first. Then redeploy through your IaC tool. AWS treats each subscription as unique based on topic ARN, protocol, and endpoint. It won’t create a duplicate even if the attributes differ slightly.

Missing IAM Permissions on Account B

This one confuses people the most. The SNS topic policy in Account A grants account-level permission. However, the IAM user or role in Account B still needs sns:Subscribe in their own policy. It’s a two-layer check — both the resource policy and the identity policy must allow it.

Make sure the role or user in Account B includes at least this statement:

{
  "Effect": "Allow",
  "Action": "sns:Subscribe",
  "Resource": "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
}

Messages Not Arriving in the Queue

When the subscription is confirmed but messages aren’t showing up, the issue is almost always the SQS queue policy. Either the aws:SourceArn doesn’t match exactly, or the policy is missing. You can check CloudTrail logs in Account A for SNS:Publish delivery failures to confirm.

Also, if your SQS queue triggers a Lambda function, make sure the queue’s visibility timeout is higher than the Lambda timeout. Otherwise, messages get reprocessed multiple times.

Full CloudFormation Template for Account B

Here’s a complete template for Account B. It includes the SQS queue, queue policy, and cross-account subscription in a single stack:

AWSTemplateFormatVersion: "2010-09-09"
Description: Cross-account SNS to SQS subscription setup

Parameters:
  SNSTopicArn:
    Type: String
    Description: ARN of the SNS topic in Account A
    Default: "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"

Resources:
  MessageQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: cross-account-messages
      VisibilityTimeout: 300
      MessageRetentionPeriod: 1209600

  MessageQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref MessageQueue
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowSNSSendMessage
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: "sqs:SendMessage"
            Resource: !GetAtt MessageQueue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: !Ref SNSTopicArn

  SNSSubscription:
    Type: AWS::SNS::Subscription
    DependsOn: MessageQueuePolicy
    Properties:
      TopicArn: !Ref SNSTopicArn
      Protocol: sqs
      Endpoint: !GetAtt MessageQueue.Arn
      RawMessageDelivery: true

Outputs:
  QueueArn:
    Value: !GetAtt MessageQueue.Arn
  QueueUrl:
    Value: !Ref MessageQueue
  SubscriptionArn:
    Value: !Ref SNSSubscription

Deploy the stack with this command:

aws cloudformation deploy \
  --template-file cross-account-sns-sqs.yaml \
  --stack-name cross-account-sns-sqs \
  --parameter-overrides SNSTopicArn="arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"

Adding a Dead-Letter Queue

For production, add a dead-letter queue (DLQ). This catches messages that your consumer fails to process. Instead of disappearing, failed messages move to the DLQ after a set number of retries.

Add these resources to your CloudFormation template:

DeadLetterQueue:
  Type: AWS::SQS::Queue
  Properties:
    QueueName: cross-account-messages-dlq
    MessageRetentionPeriod: 1209600

MessageQueue:
  Type: AWS::SQS::Queue
  Properties:
    QueueName: cross-account-messages
    VisibilityTimeout: 300
    MessageRetentionPeriod: 1209600
    RedrivePolicy:
      deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
      maxReceiveCount: 3

With maxReceiveCount: 3, a message moves to the DLQ after three failed attempts. You can then set up a CloudWatch alarm on the DLQ to get notified when messages land there.

Wrapping Up

Cross-account SNS to SQS is not complicated once you know the pattern. The SNS topic policy lets Account B subscribe. The SQS queue policy lets SNS deliver messages. And the subscription ties the two together. Most issues come from either missing IAM permissions on Account B or a mismatched ARN in the SQS queue policy.

For more cross-account patterns, check out setting up cross-account S3 uploads with Lambda or copying S3 bucket objects across AWS accounts.

Categories AWS Tags AWS, CloudFormation, Cross-Account, IAM, Lambda, SNS, SQS
How to Avoid AWS Lambda Timeout When Processing HubSpot Records
How to Fix SQS Visibility Timeout in AWS Lambda
← PreviousHow to Avoid AWS Lambda Timeout When Processing HubSpot RecordsNext →How to Fix SQS Visibility Timeout in AWS Lambda

Related Articles

How to Add Swap Space on EC2 Ubuntu 22.04
AWS

How to Add Swap Space on EC2 Ubuntu 22.04

How to Avoid Unexpected AWS Lambda Costs
AWS

How to Avoid Unexpected AWS Lambda Costs

How to Copy S3 Bucket Objects Across AWS Accounts
AWS

How to Copy S3 Bucket Objects Across AWS Accounts

© 2026 Linuxbeast • Built with GeneratePress
Manage Consent
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes. The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
View preferences
  • {title}
  • {title}
  • {title}