Overview
Large Apple Health XML exports upload directly to object storage (any S3-compatible bucket: AWS S3 or self-hosted) rather than streaming through the API. How a finished upload starts processing is controlled byAPPLE_XML_UPLOAD_COMPLETION_MODE:
client(default) - the portal finalizes the upload and queues processing itself. You only need an S3 bucket, credentials, and a CORS policy. The SNS steps (2-5) are not required.sns- an S3 bucket event notifies the backend through SNS. This is an AWS-only legacy path; complete every step in this guide.
client mode - including any non-AWS S3-compatible deployment - do Step 1 and Step 6 and skip the SNS-only steps (2-5). See the Apple XML Import Guide for the upload flow itself.Prerequisites
- AWS account with appropriate permissions
- AWS CLI installed (optional, but recommended)
- Access to AWS Console
Step 1: Create S3 Bucket
Open S3 Console
Create Bucket
- Bucket name: Choose a unique name (e.g.,
open-wearables-xml) - AWS Region: Select your preferred region (e.g.,
eu-north-1) - Block Public Access: Keep all boxes checked (recommended)
- Bucket Versioning: Disabled (optional)
- Encryption: Enable with SSE-S3 (recommended)
Create Bucket
Using AWS CLI
Using AWS CLI
Configure Bucket CORS (required for portal uploads)
The portal uploads files directly from the browser to the bucket, which is a cross-origin request. Without a CORS policy the browser blocks the upload, and even a successfulPUT
hides the ETag the client needs to finalize a multipart upload. This applies to both
completion modes and to any S3-compatible bucket.
Apply a CORS policy that allows your portal’s origin to PUT/POST and exposes the ETag
response header:
Using AWS CLI
Using AWS CLI
AllowedOrigins to your own portal domain (avoid *) as
defense in depth, and keep AllowedMethods to only PUT/POST.sns completion mode. Running the default client mode? Skip
ahead to Step 6 - you need the bucket above, its CORS
policy, and credentials, but no SNS topic, subscription, or S3 event notification.Step 2: Create SNS Topic (SNS mode only)
Open SNS Console
Create Topic
- Type: Standard
- Name: e.g.,
owear
Create Topic
Copy Topic ARN
Using AWS CLI
Using AWS CLI
Step 3: Update SNS Topic Access Policy (SNS mode only)
The SNS topic needs permission to receive publish events from S3.Open Topic Access Policy
Add S3 Publish Permission
Statement array (update the ARNs with your values):Save Changes
Step 4: Add S3 Event Notification (SNS mode only)
Configure S3 to send notifications to the SNS topic when files are uploaded.Open Bucket Properties
Create Event Notification
- Name: e.g.,
xml-upload-notification - Event types: Check All object create events (or specifically
s3:ObjectCreated:Post) - Suffix:
.xml(optional, to only notify for XML files) - Destination: Select SNS topic
- SNS topic: Select your topic (e.g.,
owear)
Save Configuration
Using AWS CLI
Using AWS CLI
Step 5: Create SNS Subscription (SNS mode only)
Create an HTTPS subscription so SNS delivers notifications to your backend.Open Topic Subscriptions
Configure Subscription
- Topic ARN: Select your topic (e.g.,
owear) - Protocol: HTTPS
- Endpoint: Your backend’s SNS notification URL, e.g.:
Create Subscription
SubscriptionConfirmation request to your endpoint. The backend handles this automatically and confirms the subscription.Verify Confirmation
Step 6: Update Environment Variables
Add the AWS configuration to your.env file:
Optional: S3-compatible storage and upload tuning
These variables are all optional; their defaults target real AWS S3. Set the endpoint variables to run the same upload flow against a self-hosted S3-compatible server, and the rest to tune upload behavior.How to Get AWS Credentials
How to Get AWS Credentials
Create IAM User with S3 and SNS Permissions
- Go to IAM Console
- Click Users → Add users
- User name:
open-wearables-app - Select Access key - Programmatic access
- Click Next: Permissions
- Attach policies:
AmazonS3FullAccess(or create a custom policy with specific bucket access)AmazonSNSFullAccess(SNS mode only, or create a custom policy with specific topic access). A custom SNS policy must includeSNS:ConfirmSubscription- the backend calls it to confirm the HTTPS subscription; without it the subscription stays on “Pending confirmation”.
- Click through to create the user
- Copy the Access key ID and Secret access key immediately (you won’t see the secret again)
Step 7: Restart Services
After updating the.env file, restart your services:
Verify Setup
Test your configuration:Check SNS Subscription (SNS mode only)
Test Upload
Check Backend Logs
process_aws_upload tasks - in client mode these are queued from the /complete call, in sns mode from the incoming SNS notification.Troubleshooting
SNS Subscription Stuck on Pending
SNS Subscription Stuck on Pending
- Backend is not publicly accessible
- Endpoint URL is incorrect
- IAM user is missing
SNS:ConfirmSubscription(common if the user was set up before the SQS→SNS migration and only hasAmazonSQSFullAccess) - Backend returned an error during confirmation
- Verify your backend is reachable from the internet
- Check the endpoint URL matches
/api/v1/sns/notification - Ensure the IAM user has
SNS:ConfirmSubscription(attachAmazonSNSFullAccessor add it to your custom policy) - Check backend logs for confirmation errors
- Delete the subscription and create a new one
Notifications Not Triggering Processing
Notifications Not Triggering Processing
- S3 event notification not configured correctly
- SNS topic access policy doesn’t allow S3 to publish
- Wrong bucket or topic ARN in configuration
- Verify the event notification in S3 bucket properties
- Check the SNS access policy allows
s3.amazonaws.comto publish - Test by manually uploading a file to S3 and checking backend logs
Access Denied Errors
Access Denied Errors
- IAM user doesn’t have required permissions
- Wrong AWS credentials in
.env - Bucket or topic in different region
- Verify IAM user has S3 and SNS permissions
- Double-check
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY - Ensure
AWS_REGIONmatches where your bucket and topic are located
Import fails with 'S3 client not configured'
Import fails with 'S3 client not configured'
- Set
AWS_BUCKET_NAME,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY, andAWS_REGION(plus anyAWS_ENDPOINT_URL) on the worker service, then restart it - In Docker Compose, confirm the worker service loads the same env file as the backend
Browser upload fails with 'Load Failed' or a CORS error
Browser upload fails with 'Load Failed' or a CORS error
ETag header. Direct-from-browser uploads are cross-origin, so the preflight is rejected (or the client cannot read part ETags to complete a multipart upload).Fix:- Apply the bucket CORS policy: allow your portal origin with
PUT/POSTand setExposeHeaders: ["ETag"] - Confirm
AllowedOriginsmatches the exact scheme + host + port the portal is served from
Cost Optimization
S3 Storage
- Uploaded XML files remain in S3 for your records
- Set up Lifecycle Rules to archive or delete old files if needed
- Consider moving to Glacier for long-term archival
SNS Notifications
- SNS free tier includes 100,000 HTTP/HTTPS notifications per month
- Check AWS SNS pricing for current limits and rates beyond free tier
Data Transfer
- Keep S3 bucket and servers in the same AWS region
- Use presigned URLs to avoid routing data through your server
Request Costs
- S3 and SNS costs are negligible for typical usage
- See AWS S3 pricing for current rates
Security Best Practices
- Use IAM Roles: If running on EC2/ECS, use IAM roles instead of access keys
- Restrict Bucket Access: Only allow specific IAM users/roles to access the bucket
- Enable Encryption: Use SSE-S3 or SSE-KMS for data at rest
- Monitor Access: Enable CloudTrail logging for S3 and SNS
- Rotate Credentials: Regularly rotate AWS access keys
Related Guides
- Apple XML Import Guide - Using the presigned URL endpoint
- How to Add a Provider - Extending the platform
- Integration Guide - Testing your setup

