Skip to main content

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