
ensambladorensambladorFollow AWS Specialist Solutions...
Your customers are already on X. They follow your brand, engage with your posts, and when they need help — they send a DM. If your support team has to switch between X and their contact center tool, you're losing time and context.
In this blog, you'll learn how to connect X Direct Messages directly to Amazon Connect Chat, so your agents can handle X conversations from the same workspace they use for every other channel. No app switching, no copy-pasting, no lost messages.
Check out the code at Github
A bidirectional messaging bridge between X DMs and Amazon Connect that:
The end result: agents see X conversations as regular chat contacts in their Amazon Connect workspace, complete with the customer's X display name and profile information.
Here's how it flows:
When a customer sends a DM to your business X account, the inbound path handles everything from webhook validation to message delivery.
X uses a Challenge-Response Check (CRC) to verify webhook ownership — this is fundamentally different from Meta's approach used in the Instagram and Facebook Messenger integrations. Instead of comparing a shared secret string, X sends a crc_token that must be hashed with your Consumer Secret using HMAC-SHA256:
def compute_crc_response(crc_token, consumer_secret):
digest = hmac.new(
consumer_secret.encode('utf-8'),
crc_token.encode('utf-8'),
hashlib.sha256
).digest()
encoded_hash = base64.b64encode(digest).decode('utf-8')
return {"response_token": f"sha256={encoded_hash}"}
X sends this challenge both during initial webhook registration and periodically afterward to re-validate. The Lambda handles it automatically on every GET request.
For POST requests (actual DM events), the XService class parses the direct_message_events payload. Each event contains the sender ID, recipient ID, text content, and any media attachments:
class XMessage:
def __init__(self, event_data):
message_create = event_data.get('message_create', {})
message_data = message_create.get('message_data', {})
self.sender_id = message_create.get('sender_id')
self.text = message_data.get('text')
self.recipient_id = message_create.get('target', {}).get('recipient_id')
# Parse attachment if present
self.attachment = message_data.get('attachment')
if self.attachment and self.attachment.get('type') == 'media':
media = self.attachment.get('media', {})
self.attachment_url = media.get('media_url_https')
self.attachment_type = media.get('type') # photo, animated_gif, video
The service filters out messages sent by your own X account ID to prevent echo loops — when your account sends a reply, X also delivers it as a webhook event.
X webhook payloads include inline user profile data in a users dictionary, which the service extracts first. For any missing profiles, it falls back to a three-tier lookup:
def get_user_profile(self, user_id):
# Check in-memory cache first
if user_id in self.user_profiles:
return self.user_profiles[user_id]
# Check DynamoDB users table
if USERS_TABLE_NAME:
users_table = TableService(table_name=USERS_TABLE_NAME)
db_profile = users_table.get_item({"id": user_id})
if db_profile:
return db_profile
# Fetch from X API via Tweepy as last resort
client = tweepy.Client(
consumer_key=credentials.get('consumer_key'),
consumer_secret=credentials.get('consumer_secret'),
access_token=credentials.get('access_token'),
access_token_secret=credentials.get('access_token_secret')
)
response = client.get_user(id=user_id, user_fields=['name', 'username', 'profile_image_url'])
# ... cache in DynamoDB with 7-day TTL
The profile includes name, username, and profile_image_url. Profiles are cached in a DynamoDB table with a 7-day TTL, so repeat conversations skip the API call entirely.
The handler checks DynamoDB for an existing chat session using the sender's X user ID:
connectionToken. If the token is expired (AccessDeniedException), it automatically creates a new session.StartChatContact to create a new Amazon Connect Chat, starts contact streaming to the SNS topic, creates a participant connection, and stores everything in DynamoDB.The contact attributes include the channel name ("X"), the customer ID, and the customer's display name — making it easy to identify the source channel in Contact Flows and agent routing.
When a customer sends an image, GIF, or video, the handler downloads it from X's CDN and uploads it to the Connect Chat session. X media URLs come in two flavors:
pbs.twimg.com — publicly accessible, downloaded directlyton.twitter.com — requires OAuth 1.0a authentication (using requests_oauthlib)The upload uses the three-step Participant API flow: start_attachment_upload → PUT to pre-signed URL → complete_attachment_upload. If anything fails, the handler falls back to sending the media URL as a text message.
For attachments that include a caption, the text is cleaned up by stripping the auto-appended t.co media link that X adds to the message body.
When an agent replies from the Amazon Connect workspace, the outbound path delivers the message back to X.
Amazon Connect publishes chat streaming events to an SNS topic. The Outbound Handler Lambda subscribes to this topic and processes three event types:
MESSAGE — text messages from the agentATTACHMENT — file attachments sent by the agentEVENT — participant join/leave and chat ended eventsMessages from the CUSTOMER role are skipped to avoid processing the customer's own messages again.
For text messages with CUSTOMER or ALL visibility, the handler looks up the customer's X user ID from DynamoDB and sends the reply via the Tweepy v2 API:
def send_x_text(credentials, text, recipient_id):
client = tweepy.Client(
consumer_key=credentials["consumer_key"],
consumer_secret=credentials["consumer_secret"],
access_token=credentials["access_token"],
access_token_secret=credentials["access_token_secret"],
)
response = client.create_direct_message(
participant_id=recipient_id,
text=text,
)
return response
When an agent sends a file from the Connect Chat widget, the handler retrieves a signed URL for the attachment, downloads it, and uploads it to X via the v1.1 media upload endpoint (OAuth 1.0a). The media ID is then used to send a DM with the attachment:
| MIME type | X media category | Upload method |
|---|---|---|
image/jpeg, image/png, image/webp
|
dm_image |
media_upload |
image/gif |
dm_gif |
chunked_upload |
video/mp4 |
dm_video |
chunked_upload |
| everything else | — | Sent as plain-text link |
X DMs only support images and videos as native media. Unsupported types (PDFs, documents, etc.) are sent as plain-text URLs so the customer still has access to the content.
When a participant leaves or the chat ends, the handler deletes the connection record from DynamoDB so the next inbound message starts a fresh session.
| Direction | Text | Images | Videos | GIFs |
|---|---|---|---|---|
| Inbound (customer → agent) | ✅ | ✅ | ✅ | ✅ |
| Outbound (agent → customer) | ✅ | ✅ | ✅ | ✅ |
Unsupported media types (PDFs, documents, etc.) are sent as plain-text links in the outbound direction.
| Resource | Service | Purpose |
|---|---|---|
/webhooks endpoint (GET & POST) |
API Gateway | Receives X CRC challenges (GET) and inbound DM events (POST) |
| Inbound Handler | Lambda | Processes X DM events and routes them to Amazon Connect Chat |
| Outbound Handler | Lambda | Sends agent replies back to X as DMs via the Tweepy SDK |
| Active Connections table | DynamoDB | Tracks open chat sessions (contactId PK, userId GSI) |
| X Users table | DynamoDB | Caches X user profiles (TTL-based expiry, 7 days) |
messages_out topic |
SNS | Delivers Amazon Connect streaming events to the Outbound Handler |
x-dm-credentials |
Secrets Manager | Stores X API OAuth 1.0a credentials (Consumer Key, Consumer Secret, Access Token, Access Token Secret) |
/x/dm/config |
SSM Parameter Store | Holds Connect instance ID, contact flow ID, and X account ID |
/x/dm/webhook/url |
SSM Parameter Store | Stores the deployed API Gateway callback URL |
Example scenario: 1,000 conversations per month, averaging 10 messages each (5 inbound + 5 outbound), totaling 10,000 messages.
| Component | Estimated Monthly Cost | Notes |
|---|---|---|
| Infrastructure (API GW, Lambda, DynamoDB, SNS, Secrets Manager) | ~$0.71 | Negligible at this scale |
| Amazon Connect Chat (Inbound) | $20.00 | 5,000 msgs × $0.004/msg |
| Amazon Connect Chat (Outbound) | $20.00 | 5,000 msgs × $0.004/msg |
| X API — Outbound DMs | ~$50.00 | 5,000 DM sends × ~$0.01/request |
| Total | ~$90.71 |
X API uses credit-based pay-per-use pricing. The per-endpoint cost shown above is approximate — actual rates are displayed in the X Developer Console and may change. See Amazon Connect pricing and X API pricing for current rates.
To reduce Connect Chat costs on high-volume conversations, consider adding a message buffering layer to aggregate rapid consecutive messages.
Before getting started you'll need:
You need an X Developer Account with at least the Pay-Per-Use tier, and four OAuth 1.0a credentials (Consumer Key, Consumer Secret, Access Token, Access Token Secret).
See the X Platform Setup Guide for detailed step-by-step instructions on creating your app, configuring permissions, and generating credentials.
⚠️ Important: The free tier does not include webhook-based DM delivery. You need the Pay-Per-Use tier.
You need an Amazon Connect instance. If you don't have one yet, you can follow this guide to create one.
You'll need the INSTANCE_ID of your instance. You can find it in the Amazon Connect console or in the instance ARN:
arn:aws:connect:<region>:<account_id>:instance/INSTANCE_ID
Create or have ready the contact flow that defines the user experience. Follow this guide to create an Inbound Contact Flow. The simplest one will work.
Remember to publish the flow.
Take note of the INSTANCE_ID and CONTACT_FLOW_ID from the Details tab. The values are in the flow ARN:
arn:aws:connect:<region>:<account_id>:instance/INSTANCE_ID/contact-flow/CONTACT_FLOW_ID
(see the Amazon Connect Prerequisites for more details)
⚠️ Deploy in the same region where your Amazon Connect instance is configured.
git clone https://github.com/aws-samples/sample-amazon-connect-social-integration.git
cd sample-amazon-connect-social-integration/x-dm-connect-chat
Follow the instructions in the CDK Deployment Guide for environment setup and deployment commands.
After deployment, three configuration steps are needed:
Update X API Credentials — The stack creates a Secrets Manager secret named x-dm-credentials with placeholder values. Update it with your actual Consumer Key, Consumer Secret, Access Token, and Access Token Secret.
Update SSM Configuration — Update the SSM parameter /x/dm/config with your Amazon Connect instance_id, contact_flow_id, and your X account's numeric x_account_id.
Register the Webhook and Subscribe — Register your deployed API Gateway URL with the X Account Activity API and subscribe your business account to receive DM events. The Inbound Handler responds to CRC challenges automatically.
For detailed instructions on each step, including how to find your x_account_id and register the webhook, see the project README and the X Platform Setup Guide.
Go to your Amazon Connect instance and open the Contact Control Panel (CCP).
Try these scenarios:
X supports end-to-end encrypted (E2EE) Direct Messages. However, encrypted DMs are not accessible via the X API. This integration only processes standard (non-encrypted) DMs. If a conversation is encrypted, the webhook will not receive those message events.
This solution handles the core X DM-to-Connect messaging flow. Some ideas to extend it:
This solution already fetches X profile data (name, username, profile image) and passes it as contact attributes. You can take this further by integrating with Amazon Connect Customer Profiles to give agents a unified view of the customer across channels. Then in your Contact Flow, use the Customer Profiles block to retrieve the profile and display it in the agent workspace. The agent sees the customer's name, X handle, and any previous interaction history — all before they even type a reply.