Now Available: Packet Sources - Proxylity Blog
Now Available: Packet Sources
By Lee Harding | June 8, 2026 | 6 min read
Packet Sources are now generally available to all customers with an AWS Marketplace subscription.<br>With a Packet Source, your application can send UDP packets to connected clients at any time —<br>without waiting for those clients to send a request first. Push notifications, game state updates,<br>over-the-air firmware delivery, async acknowledgements: they're all straightforward to build now,<br>on top of the same Listeners you already use for inbound traffic.
We announced the feature in Select Availability<br>back in April. This post covers what's changed since then and what you need to know to<br>get started.
What Changed Since Select Availability
The design described in the original article — an SNS topic in your account bound to a Listener via<br>a CloudFormation resource — is the same. But several things have been refined or added since April.
Message format
Each SNS message body must now wrap packets in a Messages array. This unlocks fan-out:<br>a single SNS publish can deliver to multiple clients in one operation, which is useful for<br>broadcasting game state or sensor commands to a group of peers.
"Messages": [<br>"Data": "AQIDBA==",<br>"Remote": { "Address": "203.0.113.42", "Port": 4567 }
The Remote object uses the field name Address (not IP, as shown<br>in the earlier article). The rest of the structure is unchanged.
EgressRegion — required message attribute
This is the most important operational detail. Proxylity subscribes one SQS delivery queue per<br>supported AWS region to your SNS topic, each with a filter policy keyed on the<br>EgressRegion SNS message attribute. You must set this attribute on every publish;<br>without it the message matches no filter and is silently discarded — no packet is delivered.
Set EgressRegion to the AWS region where you want the packet to exit the network.<br>For replies to inbound packets, use the IngressRegion value from the original packet<br>JSON — this ensures the reply takes the same regional path the client's traffic entered, which is<br>required for WireGuard session compatibility.
import boto3, json, base64
sns = boto3.client("sns")<br>payload = b"\x01\x02\x03\x04"
sns.publish(<br>TopicArn=REPLY_TOPIC_ARN,<br>MessageAttributes={<br>"EgressRegion": {"DataType": "String", "StringValue": "us-east-1"}<br>},<br>Message=json.dumps({<br>"Messages": [<br>"Data": base64.b64encode(payload).decode(),<br>"Remote": {"Address": "203.0.113.42", "Port": 4567}<br>})
All formatters are supported
The select availability release only supported base64 encoding. The GA release supports<br>all the same formatters available to Destinations: base64, hex,<br>utf8, ascii, and coap. Specify the optional<br>Formatter field in each message entry; it defaults to base64 when<br>omitted.
WireGuard Support
Packet Sources work with both plain UDP Listeners and WireGuard Listeners. For WireGuard you set<br>Remote.PeerKey to the base64-encoded public key of the target peer; the Gateway looks<br>up the active WireGuard session for that key and encrypts the packet before delivery. If no session<br>exists for the key, the packet is dropped.
If your WireGuard Listener has DecapsulatedDelivery enabled, sourced packets must<br>also include an Inner block with inner IP/UDP addressing so the Gateway can<br>re-encapsulate the payload correctly before sending it through the tunnel:
"Messages": [<br>"Data": "AQIDBA==",<br>"Remote": {<br>"Address": "203.0.113.42",<br>"Port": 51820,<br>"PeerKey": "base64encodedWireGuardPublicKey=="<br>},<br>"Inner": {<br>"SourceAddress": "10.0.0.1",<br>"SourcePort": 53,<br>"DestinationAddress": "10.0.0.2",<br>"DestinationPort": 12345
Setting Up a Packet Source
The CloudFormation resource is Custom::ProxylityUdpGatewayPacketSource. It binds an<br>SNS topic you own to a named Listener. Proxylity assumes the role you provide to subscribe its<br>delivery queues to the topic; those subscriptions are removed automatically when you delete the<br>resource.
The IAM policy for the role must be attached before CloudFormation attempts to create the Packet<br>Source, so use DependsOn to enforce the ordering:
ReplyTopic:<br>Type: AWS::SNS::Topic
ProxylitySubscribeToTopicPolicy:<br>Type: AWS::IAM::Policy<br>Properties:<br>PolicyName: ProxylitySubscribeToTopicPolicy<br>PolicyDocument:<br>Version: "2012-10-17"<br>Statement:<br>- Effect: Allow<br>Action:<br>- sns:Subscribe<br>- sns:Unsubscribe<br>- sns:ListSubscriptionsByTopic<br>Resource: !GetAtt ReplyTopic.TopicArn<br>Roles:<br>- !Ref RoleForProxylity
ReplySource:<br>DependsOn:<br>- ProxylitySubscribeToTopicPolicy<br>Type: Custom::ProxylityUdpGatewayPacketSource<br>Properties:<br>ServiceToken: !FindInMap [ProxylityConfig, !Ref "AWS::Region", ServiceToken]<br>ApiKey: !FindInMap [ProxylityConfig, Account, ApiKey]<br>ListenerName: !Ref MyListener<br>SnsTopicSource:<br>TopicArn: !GetAtt ReplyTopic.TopicArn<br>Role: !GetAtt RoleForProxylity.Arn
Example: CoAP Observe with a WireGuard Listener
The best place to start is the<br>CoAP Time Service<br>in the Proxylity examples repository. It deploys a fully working CoAP server...