Control your self-hosted Home Assistant with Amazon Alexa—without a subscription.
- What Is This?
- Why Does This Exist?
- Features
- Prerequisites
- Quick Start
- Installation
- Cloudflare Tunnel Setup
- Configure Alexa Skill
- Home Assistant Configuration
- Enable Skill and Link Account
- Architecture
- Development
- Troubleshooting
- Security Considerations
- Contributing
- Support This Project
- References
- License
This project enables native Alexa Smart Home integration for self-hosted Home Assistant installations. Say "Alexa, turn on the living room lights" and control your HA devices directly through Amazon's official Smart Home API.
Self-hosted Home Assistant needs a publicly accessible endpoint with OAuth 2.0, HTTPS, and complex security to talk to Alexa's cloud services. While subscription services exist, this project provides a free, open-source alternative where you maintain complete control:
- ✅ Your infrastructure - AWS Lambda and Cloudflare Tunnel under your account
- ✅ Your data - Commands flow through your own servers, not a third party
- ✅ Your security - You decide encryption, access controls, and audit logging
- ✅ Your privacy - No vendor can monetize, analyze, or share your usage patterns
⚠️ Your responsibility - You handle setup, updates, and troubleshooting
Best for: Self-hosters who want to learn AWS infrastructure, prefer no subscriptions, or are already running Cloudflare Tunnel.
- Smart Home Control - Forward Alexa directives to Home Assistant (Discovery, PowerControl, BrightnessControl, etc.)
- Stateless OAuth - JWT-based authorization flow
- Zero-Trust Security - Cloudflare Access authentication for all Home Assistant requests
- Token Sanitization - Automatic redaction of sensitive data in logs
- Encrypted Secrets - SecureString storage in AWS Parameter Store with KMS encryption
- Python 3.13+ (for running deployment script)
- AWS Account
- Self-hosted Home Assistant with Alexa integration
- Cloudflare Account (free tier)
- Domain or subdomain you control (e.g.,
homeassistant.yourdomain.com) - AWS SAM CLI
- Development dependencies (
pip install -e ".[dev]") - for linting, testing, type checking
- Setup Cloudflare Tunnel for Home Assistant
- Clone and deploy Lambda functions using AWS SAM
- Create Alexa skill in Developer Console
- Configure Home Assistant for Alexa
- Enable skill in Alexa app and discover devices
git clone https://github.com/tjbaker/ha-alexa.git
cd ha-alexapython3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"Prerequisites:
- AWS SAM CLI installed (
brew install aws-sam-cli) - AWS credentials configured (
aws configure) - Cloudflare Access service token (complete Cloudflare Tunnel Setup first)
Why a Custom Deployment Script?
This project uses deploy.py instead of sam deploy --guided for maximum security:
The Problem: CloudFormation's AWS::SSM::Parameter resource type only supports Type: String, not Type: SecureString. This means:
- ❌ Secrets stored as plain text in Parameter Store
- ❌ Secrets visible in AWS console
- ❌ Secrets logged in CloudFormation events/change sets
The Solution: Our deploy.py script:
- ✅ Creates parameters externally as
SecureStringwith KMS encryption - ✅ Secrets never visible in AWS console (encrypted at rest)
- ✅ Secrets never logged in CloudFormation
- ✅ CloudFormation only receives parameter paths, not values
# Run the secure deployment script
python3 deploy.pyThe script will prompt you for:
- Stack name (default:
ha-alexa) - loaded fromsamconfig.tomlif exists - AWS Region (default:
us-east-1) - Home Assistant URL (e.g.,
https://homeassistant.yourdomain.com) - Alexa Skill ID (
amzn1.ask.skill.xxx- get from Alexa Developer Console) - Alexa Vendor ID (extracted from Alexa redirect URIs, see below)
- Verify SSL (default:
true) - Debug Mode (default:
false) - Cloudflare Client ID (secret - not echoed)
- Cloudflare Client Secret (secret - not echoed)
- OAuth JWT Secret (secret - generate with
openssl rand -base64 32)
💡 Tip: On subsequent deployments, the script loads defaults from samconfig.toml - just press Enter to accept existing values and only re-enter the 3 secrets.
The script will:
- Create SecureString parameters in Parameter Store with KMS encryption
- Build the Lambda package with
sam build - Deploy the CloudFormation stack with
sam deploy - Display the function URLs you need for Alexa configuration
View deployment outputs:
sam list stack-outputs --stack-name ha-alexaTo delete everything:
python3 deploy.py # Choose "delete" when prompted
sam delete --stack-name ha-alexaClick to expand Cloudflare setup instructions
For Home Assistant OS or Supervised (Recommended):
-
Install Add-on
- Settings > Add-ons > Add-on Store
- Search for "Cloudflare Tunnel"
- Install the official Cloudflare add-on
-
Configure Add-on
- Get Cloudflare Tunnel token from Cloudflare Zero Trust dashboard
- Add token to add-on configuration
- Start the add-on
-
Setup DNS
- In Cloudflare dashboard, point your domain to the tunnel
- Example:
homeassistant.yourdomain.com→ your tunnel
Note: Alternative installation methods exist for Home Assistant Container/Core (manual cloudflared installation), but are not included here for brevity. See Cloudflare Tunnel documentation for manual setup options.
In Cloudflare Zero Trust dashboard:
-
Create Service Token First
- Zero Trust > Access > Service Auth > Service Tokens
- Click "Create Service Token"
- Name:
Lambda Functions - Save the Client ID and Client Secret (you'll need these for SAM deployment)
-
Create Application
- Zero Trust > Access > Applications > Add an application
- Type: Self-hosted
- Name:
Home Assistant - Domain:
homeassistant.yourdomain.com - Path: Leave empty (protects entire subdomain)
-
Configure Application Policies
You need TWO policies - one for Lambda service tokens, one for authorized users:
Policy 1: Lambda Service Token
- Name:
Lambda Service Token - Action: Bypass
- Rule type: Service Auth
- Selector:
Lambda Functions(the token you created)
What "Bypass" means: When Lambda sends valid service token headers (
CF-Access-Client-IdandCF-Access-Client-Secret), Cloudflare validates them and allows access WITHOUT requiring interactive login. This is for machine-to-machine authentication.Policy 2: Authorized User Access
- Name:
HA Admin Access - Action: Allow
- Rule type: Emails
- Value: Add all allowed user email addresses (e.g.,
[email protected],[email protected]) - Alternative: Use "Email domain" rule for
@yourdomain.comto allow all users at your domain
What "Allow" means: Users must authenticate with Cloudflare Access (login with their email via One-Time PIN, Google, etc.) BEFORE accessing Home Assistant. This adds a security layer before reaching your HA instance.
Save the application with both policies.
Security Model:
- Your entire Home Assistant instance is protected by Cloudflare Access
- Lambda functions authenticate automatically using service tokens (bypass policy)
- You authenticate with your email/Cloudflare Access when accessing HA from a browser (allow policy)
- Nothing is exposed to the public internet without authentication
- Name:
- Go to Alexa Developer Console
- Create Skill > Smart Home > Custom Model
- Note the Skill ID (starts with
amzn1.ask.skill.)
- Default Endpoint: Your
AlexaSmartHomeFunctionArnfrom SAM deployment - Payload version: v3
- Account Linking: Required (configure below)
In the Alexa Developer Console, go to your skill's Account Linking section:
- Authorization Grant Type: Auth Code Grant
- Authorization URI: Your
AlexaAuthorizeFunctionUrlOutput - Access Token URI: Your
AlexaOAuthFunctionUrl - Your Client ID: Use the same value as Authorization URI
- Your Secret: Generate a random string with
openssl rand -base64 32(Home Assistant ignores this, but Alexa requires it) - Scope:
smart_home - Domain List: Your Home Assistant domain (e.g.,
homeassistant.yourdomain.com)
After saving, scroll down to view Redirect URIs generated by Alexa:
https://pitangui.amazon.com/api/skill/link/ABCD1234EFGH
https://alexa.amazon.com/api/skill/link/ABCD1234EFGH
https://layla.amazon.com/api/skill/link/ABCD1234EFGH
Copy the text after /link/ (e.g., ABCD1234EFGH) - this is your AlexaVendorId for SAM deployment.
- Go to Test > Enable for testing in "Development"
- Save and deploy skill
Add or update the following sections in your configuration.yaml:
# Core Configuration
homeassistant:
external_url: https://homeassistant.yourdomain.com
# HTTP Configuration
http:
use_x_forwarded_for: true
trusted_proxies:
- 173.245.48.0/20 # Cloudflare IPs
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 141.101.64.0/18
- 108.162.192.0/18
- 190.93.240.0/20
- 188.114.96.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 162.158.0.0/15
- 104.16.0.0/13
- 104.24.0.0/14
- 172.64.0.0/13
- 131.0.72.0/22
- 172.30.32.0/23 # Add your Docker/local network CIDR if applicable
cors_allowed_origins:
- https://alexa.amazon.com
- https://layla.amazon.com
- https://pitangui.amazon.com
- https://alexa.amazon.co.jp
# Alexa Integration
alexa:
smart_home:Restart Home Assistant.
- Open Alexa app
- More > Skills & Games > Your Skills > Dev
- Find your skill and Enable
- When prompted, log in to Home Assistant
- After linking completes, Alexa will automatically discover all supported devices
Supported Device Types:
This integration forwards all requests to Home Assistant's native Alexa integration, which supports:
- Lights (on/off, brightness, color) - ✅ Tested
- Switches
- Fans (on/off, speed)
- Climate/Thermostats (temperature, mode)
- Locks
- Covers (blinds, garage doors)
- Scenes
- Scripts
- Media Players
- Sensors (temperature, contact, motion - for routines)
See Home Assistant Alexa documentation for complete device support details.
You can also manually trigger discovery by saying "Alexa, discover devices" or through the Alexa app (Devices > + > Add Device > Other).
Once your skill is enabled and account is linked, you can test it in the Alexa Developer Console:
- Go to your skill in the Alexa Developer Console
- Navigate to Test tab
- Enable testing in "Development" mode
- Use the test simulator to send directives:
- Type: "discover my devices"
- Type: "turn on [device name]"
- Or use the JSON Input to send custom directives
This is useful for debugging without needing to speak to an Alexa device.
Three Lambda functions work together to provide a complete Alexa Smart Home integration:
1. Smart Home Handler (alexa_smart_home_handler.py)
- Receives Alexa Smart Home directives (e.g., TurnOn, SetBrightness, Discovery)
- Forwards directives to Home Assistant's
/api/alexa/smart_homeendpoint - Authenticates using Cloudflare service tokens
- Returns properly formatted Alexa responses
2. Authorize Handler (alexa_authorize_handler.py)
- OAuth authorization endpoint for account linking
- Redirects users to Home Assistant for login
- Receives HA authorization code and wraps it in a short-lived JWT
- Redirects back to Alexa with the JWT as the authorization code
3. Token Handler (alexa_oauth_handler.py)
- OAuth token endpoint for account linking
- Verifies and unwraps JWT authorization codes
- Exchanges the embedded HA code for access/refresh tokens
- Returns tokens to Alexa for storage
Security: All sensitive values (Cloudflare credentials, JWT secrets) are stored as SecureString in AWS Parameter Store with KMS encryption (free tier).
flowchart LR
Alexa[Alexa Service]
SmartHome[Smart Home Lambda]
Auth[Authorize Lambda]
Token[Token Lambda]
CF[Cloudflare Access]
HA[Home Assistant]
Alexa -->|Directives| SmartHome
Alexa -->|OAuth Login| Auth
Alexa -->|Token Exchange| Token
SmartHome -->|Auth Headers| CF
Auth -.->|User Login| HA
Token -->|Code Exchange| CF
CF -->|Proxy| HA
sequenceDiagram
participant User
participant Alexa
participant Lambda as Smart Home Lambda
participant CF as Cloudflare Access
participant HA as Home Assistant
participant Device
User->>Alexa: "Turn on living room light"
Alexa->>Lambda: PowerControl.TurnOn directive
Lambda->>Lambda: Load config from Parameter Store
Lambda->>CF: POST /api/alexa/smart_home<br/>(with service token headers)
CF->>CF: Validate service token (Bypass policy)
CF->>HA: Forward directive
HA->>Device: Execute command
Device-->>HA: State updated
HA-->>Lambda: Alexa response
Lambda-->>Alexa: State report
Alexa->>User: "OK"
make help # Show all commands
make install-dev # Install dev dependencies
make format # Format code (black + ruff)
make lint # Run linters
make type-check # Run mypy
make test # Run tests
make test-cov # Run tests with coverage
make clean # Remove build artifacts- Type Checking: mypy with strict mode, 100% type coverage
- Linting: Ruff (50+ rule sets) + Black
- Testing: pytest with pytest-cov, pytest-mock
- Coverage: Comprehensive test suite with >90% coverage
- Verify Lambda function ARN in Alexa skill configuration
- Check CloudWatch logs for Lambda errors
- Ensure Skill ID is configured in SAM template
- Verify Function URLs are correct in Alexa skill
- Check Home Assistant is accessible via Cloudflare Tunnel
- Verify OAuth JWT secret matches in both Lambdas
- Check CloudWatch logs for both authorize and token handlers
- Verify Home Assistant Alexa integration is configured
- Check
external_urlin HA configuration - Review Lambda logs for errors during discovery
- Ensure Cloudflare service token policy is configured (Bypass action)
All Lambda function logs are stored in AWS CloudWatch Logs:
# View all functions
sam logs --stack-name ha-alexa --tail
# Specific function
sam logs --stack-name ha-alexa --name alexa-smart-home --tail
sam logs --stack-name ha-alexa --name alexa-authorize --tail
sam logs --stack-name ha-alexa --name alexa-oauth --tailDebug Mode: Enable detailed logging across Lambda functions and Home Assistant to troubleshoot the complete request flow.
Enable debug logging in Lambda functions to see:
- Complete Alexa directives (device IDs, commands, parameters)
- Full HTTP requests/responses to Home Assistant
- OAuth flow step-by-step
- All sensitive tokens are still automatically sanitized
Option 1: Via deployment script (recommended)
python3 deploy.py
# When prompted for "Enable debug mode", enter: trueOption 2: Directly in Lambda console
- Go to AWS Lambda console
- Select function (e.g.,
alexa-smart-home) - Configuration > Environment variables
- Add variable:
DEBUG=1 - Save and repeat for other functions as needed
Enable debug logging in Home Assistant to see:
- Incoming Alexa directives (device commands, discovery requests)
- Entity state mapping and capability reporting
- OAuth token validation and exchange
- Device discovery filtering and entity selection
Add to your configuration.yaml:
logger:
default: warning
logs:
homeassistant.components.alexa: debug
homeassistant.components.auth: debug # Optional: for OAuth/token debuggingThen reload the logger (no restart needed):
Developer Tools > YAML > Reload Logger
# Or via CLI: ha core logs -fWhen you enable the Alexa skill and link your account, here's the complete flow:
Step 1: Initial Authorization (alexa-authorize) - ~6 seconds
[DEBUG] Authorize request: client_id=https://...lambda-url.us-east-1.on.aws,
redirect_uri=https://pitangui.amazon.com/api/skill/link/MQTX68E7NM4GF,
scope=smart_home, response_type=code
[DEBUG] Redirecting to HA authorize endpoint: base=https://homeassistant.yourdomain.com
[INFO] Redirecting user to Home Assistant authorize endpoint
Step 2: User Authorizes with Home Assistant
- User enters Home Assistant credentials
- Home Assistant validates and redirects back to Lambda with authorization code
Step 3: Authorization Callback (alexa-authorize) - ~5ms
[DEBUG] Authorize request: code=[REDACTED], state=...
[DEBUG] Authorization completed; redirecting back to Alexa: redirect_uri=https://pitangui.amazon.com/...
[INFO] Authorization completed; redirecting back to Alexa
Step 4: Token Exchange (alexa-oauth) - ~5 seconds
[DEBUG] Token request context: domain=...lambda-url.us-east-1.on.aws, method=POST
[DEBUG] JWT unwrap successful
[DEBUG] Added Cloudflare Access authentication
[INFO] Forwarding token request to https://homeassistant.yourdomain.com/auth/token
[INFO] Token exchange successful
[DEBUG] Response: {'access_token': '[REDACTED]', 'token_type': '[REDACTED]',
'refresh_token': '[REDACTED]', 'expires_in': 1800, 'ha_auth_provider': 'homeassistant'}
Total account linking time: ~11 seconds
When you say "Alexa, turn off office desk light":
Lambda (alexa-smart-home) - ~310ms
[DEBUG] Processing event: {
'directive': {
'header': {
'messageId': 'badadc0d-6f05-4824-9052-8a18c0f3bdab',
'namespace': 'Alexa.PowerController',
'name': 'TurnOff'
},
'endpoint': {
'endpointId': 'light#office_desk_light'
}
}
}
[DEBUG] Added Cloudflare Access authentication
[INFO] Forwarding request to https://homeassistant.yourdomain.com/api/alexa/smart_home
Home Assistant - ~300ms
[DEBUG] Received Alexa Smart Home request: {
'directive': {
'header': {'namespace': 'Alexa.PowerController', 'name': 'TurnOff'},
'endpoint': {'endpointId': 'light#office_desk_light'}
}
}
[DEBUG] Sending Alexa Smart Home response: {
'event': {'header': {'namespace': 'Alexa', 'name': 'Response'}},
'context': {
'properties': [
{'name': 'powerState', 'value': 'ON'},
{'name': 'brightness', 'value': 100},
{'name': 'connectivity', 'value': {'value': 'OK'}}
]
}
}
Lambda Response - ~60ms
[INFO] Request completed successfully
[DEBUG] Response: {
'event': {
'header': {'namespace': 'Alexa', 'name': 'Response'},
'endpoint': {'endpointId': 'light#office_desk_light'}
},
'context': {
'properties': [
{'name': 'powerState', 'value': 'ON'},
{'name': 'brightness', 'value': 100},
{'name': 'colorTemperatureInKelvin', 'value': 5263},
{'name': 'connectivity', 'value': {'value': 'OK'}}
]
}
}
Total command latency: ~370ms (0.37 seconds)
Note: Home Assistant access tokens expire after 30 minutes (1800 seconds). When the token expires, Alexa automatically refreshes it via the alexa-oauth Lambda before sending the next command (adds ~5 seconds to the first command only).
- Zero-Trust Architecture: Entire Home Assistant subdomain protected by Cloudflare Access
- No Public Exposure: Nothing accessible from internet without authentication
- Encrypted Secrets: All credentials stored as SecureString in AWS Parameter Store with KMS encryption (never logged in CloudFormation)
- Service Token Authentication: Lambda functions use Cloudflare service tokens for automated access
- Least Privilege Access: Lambda functions can only read the three specific parameters they need (Cloudflare credentials, JWT secret)
- Read-Only Policy: Explicit deny prevents Lambda functions from modifying or deleting any parameters
- KMS Restrictions: Decryption only allowed via SSM service (prevents arbitrary KMS decrypt operations)
- Event Source Validation: Smart Home function validates Alexa Skill ID before processing directives
- Resource Isolation: Parameters are namespaced per stack (e.g.,
/ha-alexa/cloudflare-client-id)
- JWT Security: Authorization codes expire in 5 minutes with HMAC-SHA256 signing
- Token Sanitization: All sensitive data automatically redacted in logs
- SSL Enforcement: Certificate verification enabled by default
- Redirect Validation: OAuth redirect URIs strictly validated against Alexa domains
- Stateless Design: No persistent storage or database required
Contributions welcome! Please ensure:
- All tests pass (
make test) - Code is formatted (
make format) - No linting errors (
make lint) - Type checking passes (
make type-check) - Coverage remains above 90%
If you find this integration useful and want to say thanks, you can send me some USDC to grab a cup of coffee! ☕ Your support helps keep this project maintained and updated with new features.
💙 USDC on Base Network
Address: 0x7CC11505c5fBb8FB0c52d2f63fd9A44763246397
Network: Base (not Ethereum mainnet)
Completely optional! This project is and will always be free and open source. ❤️
- Home Assistant Alexa Integration - Official HA Alexa documentation
- Alexa Smart Home API - Amazon's Smart Home Skill API documentation
- Cloudflare Tunnel - Cloudflare Zero Trust Tunnel documentation
- AWS SAM CLI - AWS Serverless Application Model documentation
- AWS Lambda - AWS Lambda documentation
- AWS Parameter Store - AWS Systems Manager Parameter Store documentation
Copyright 2025 Trevor Baker. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
