Skip to main content

Documentation Index

Fetch the complete documentation index at: https://openwearables.io/docs/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Raw payloads storage lets you capture the exact JSON payloads received from wearable providers before any processing. This is useful when you need to:
  • Debug data issues - inspect exactly what a provider sent when something doesn’t look right
  • Test ingestion changes - replay real payloads against updated parsing logic to make sure nothing breaks
  • Reproduce bugs - grab the original payload that caused a problem and use it in local development
The feature is disabled by default and adds zero overhead when turned off.

Configuration

Add these environment variables to your .env file:
# Storage backend: "disabled" (default), "log", or "s3"
RAW_PAYLOAD_STORAGE=disabled

# Maximum payload size in bytes (default: 10 MB)
RAW_PAYLOAD_MAX_SIZE_BYTES=10485760

# S3-specific settings (only needed when using "s3" backend)
RAW_PAYLOAD_S3_BUCKET=my-raw-payloads        # Falls back to AWS_BUCKET_NAME if not set
RAW_PAYLOAD_S3_PREFIX=raw-payloads            # S3 key prefix (default: "raw-payloads")
# RAW_PAYLOAD_S3_ENDPOINT_URL=              # Optional: custom endpoint for S3-compatible providers
Raw payloads can contain sensitive health data. Make sure your storage destination meets your organization’s data protection requirements.

Storage Backends

Disabled (default)

No payloads are stored. The store_raw_payload() call returns immediately with no overhead.
RAW_PAYLOAD_STORAGE=disabled

Log

Writes each payload as a structured JSON line to stdout. Useful for development, or when your infrastructure already captures application logs (e.g., CloudWatch, Datadog).
RAW_PAYLOAD_STORAGE=log
Example output:
{
  "level": "debug",
  "message": "raw_payload",
  "source": "webhook",
  "provider": "garmin",
  "size_bytes": 4521,
  "trace_id": "a1b2c3d4",
  "payload": "{...}"
}

S3

Uploads each payload as a JSON file to an S3 bucket (or any S3-compatible object store like MinIO or Railway Object Storage).
RAW_PAYLOAD_STORAGE=s3
RAW_PAYLOAD_S3_BUCKET=my-raw-payloads
AWS credentials are reused from the existing application config (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION). See the AWS Setup guide for details.

S3 key format

Files are organized by provider, source, date, and user:
{prefix}/{provider}/{source}/{YYYY-MM-DD}/{user_id}/{file_id}.json
Example:
raw-payloads/garmin/webhook/2025-03-12/_unknown/a1b2c3d4e5f6.json
raw-payloads/apple/sdk/2025-03-12/user-123/f6e5d4c3b2a1.json
  • _unknown is used when a user ID is not available (e.g., Garmin webhooks)
  • file_id is a random 12-character hex string

S3 object metadata

Each object includes metadata for filtering and querying:
KeyDescription
sourceOrigin type (sdk, webhook)
providerProvider name (garmin, apple, samsung, etc.)
size_bytesPayload size in bytes
timestampISO 8601 UTC timestamp
user_idUser identifier (if available)
trace_idRequest trace/batch ID (if available)

Supported Ingestion Points

Raw payloads storage is currently enabled at two ingestion points:
EndpointSourceProviderTrace ID
POST /api/v1/garmin/pushwebhookgarmin8-char request trace ID
POST /api/v1/sdk/users/{user_id}/syncsdkapple, samsung, googleUUID4 batch ID

Error Handling

Raw payloads storage is designed to never break your data ingestion pipeline:
  • Oversized payloads - Payloads exceeding RAW_PAYLOAD_MAX_SIZE_BYTES are skipped with a warning log. Data processing continues normally.
  • S3 upload failures - Errors are logged but not propagated. The webhook or sync request continues processing.
  • Missing S3 bucket - If the S3 backend is configured but no bucket is available, it falls back to disabled with an error log.
  • S3 client failure - If the S3 client cannot be created (e.g., missing boto3 or invalid credentials), it falls back to disabled.

Example Setup

RAW_PAYLOAD_STORAGE=log
Then inspect payloads in Docker logs:
docker compose logs -f backend | grep raw_payload

Replaying Stored Payloads

Use the replay_raw_payloads.py script to re-send S3-stored payloads through the SDK sync endpoint. This is useful for reproducing issues, re-processing data after a fix, or load testing.
cd backend
set -a && source config/.env && set +a

uv run --with boto3,httpx python scripts/replay_raw_payloads.py \
    --user-id <source-user-uuid> \
    --api-url http://localhost:8000 \
    --api-key sk-...
The script reads AWS/S3 credentials from environment variables automatically.

Filtering

Narrow down which payloads to replay:
uv run --with boto3,httpx python scripts/replay_raw_payloads.py \
    --user-id <uuid> \
    --provider apple \
    --source sdk \
    --date-from 2025-01-01 \
    --date-to 2025-01-31 \
    --limit 10

Replaying to a different user

Use --target-user-id to send one user’s payloads to a different user account:
uv run --with boto3,httpx python scripts/replay_raw_payloads.py \
    --user-id <source-uuid> \
    --target-user-id <destination-uuid> \
    --api-url https://api.openwearables.io \
    --api-key sk-...

Dry run

Preview which payloads would be sent without making any requests:
uv run --with boto3,httpx python scripts/replay_raw_payloads.py \
    --user-id <uuid> \
    --dry-run

Options

FlagDefaultDescription
--user-idrequiredS3 user ID whose payloads to read
--target-user-idsame as --user-idUser ID to send payloads to
--api-urlhttp://localhost:8000Backend API base URL
--api-keyOPEN_WEARABLES_API_KEY env varAPI key for authentication
--s3-bucketRAW_PAYLOAD_S3_BUCKET or AWS_BUCKET_NAME env varS3 bucket name
--s3-prefixraw-payloadsS3 key prefix
--s3-endpoint-urlRAW_PAYLOAD_S3_ENDPOINT_URL env varCustom S3 endpoint for compatible providers
--aws-regionAWS_REGION env var or eu-north-1AWS region
--aws-access-key-idAWS_ACCESS_KEY_ID env varAWS access key ID
--aws-secret-access-keyAWS_SECRET_ACCESS_KEY env varAWS secret access key
--providerallFilter by provider (apple, samsung, etc.)
--sourceallFilter by source (sdk, webhook)
--date-fromnoneStart date filter (YYYY-MM-DD)
--date-tononeEnd date filter (YYYY-MM-DD)
--delay1.0Seconds between requests
--limitunlimitedMaximum number of payloads to replay
--dry-runoffList payloads without sending