Skip to content
Merged
3 changes: 2 additions & 1 deletion .env.docker
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ LOG_FORMAT=console
# Hevy settings
HEVY_API_KEY=your-hevy-api-key
HEVY_BASE_URL=https://api.hevyapp.com
HEVY_WEBHOOK_SECRET=your-hevy-webhook-secret
# When set, all REST API endpoints require the X-API-Key header with this exact value
REST_API_KEY=your-rest-api-key
HEVY_SYNC_INTERVAL_SECONDS=3600
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ MCP_PORT=9091

# Hevy API (optional — required for webhook sync and automatic workout fetching)
HEVY_API_KEY=your-hevy-api-key
HEVY_WEBHOOK_SECRET=your-hevy-webhook-secret
# When set, all REST API endpoints require the X-API-Key header with this exact value
REST_API_KEY=your-rest-api-key

# Logging
LOG_LEVEL=INFO
Expand Down
58 changes: 16 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,70 +184,44 @@ python main.py
python mcp_server_main.py
```

### API Authentication (Signature Verification)
### API Authentication (API Key)

When `HEVY_WEBHOOK_SECRET` is configured, **all REST API endpoints require signature verification**. Every request must include a valid `X-Hevy-Signature` header computed as the HMAC-SHA256 hex digest of the raw request body.
When `REST_API_KEY` is configured, **all REST API endpoints require an API key**. Every request must include the `X-API-Key` header with the exact value of `REST_API_KEY`.

If the signature is missing or invalid, the server responds with `400 Bad Request`.
If the header is missing or the key is invalid, the server responds with `400 Bad Request`.

**To disable signature verification**, leave `HEVY_WEBHOOK_SECRET` unset (default).

#### How to Compute the Signature

1. Take the raw request body (exact bytes as sent over the wire).
2. Compute `HMAC-SHA256(body, HEVY_WEBHOOK_SECRET)`.
3. Send the result as the `X-Hevy-Signature` header (hex string, no prefix).
**To disable API key verification**, leave `REST_API_KEY` unset (default).

#### Example with cURL (JSON endpoint)

```bash
BODY='{"workoutId":"abc123"}'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "your-hevy-webhook-secret" | sed 's/^.* //')

curl -X POST http://localhost:9090/webhooks/hevy \
-H "Content-Type: application/json" \
-H "X-Hevy-Signature: $SIGNATURE" \
-d "$BODY"
-H "X-API-Key: your-rest-api-key" \
-d '{"workoutId":"abc123"}'
```

#### Example with cURL (multipart file upload)

For file uploads, the signature is computed over the entire multipart body (including boundaries). The easiest way is to write the body to a file first:

```bash
# Build the multipart payload manually
cat > /tmp/payload.txt <<'EOF'
------Boundary123
Content-Disposition: form-data; name="file"; filename="hevy_export.csv"
Content-Type: text/csv

<paste CSV content here>
------Boundary123--
EOF

SIGNATURE=$(openssl dgst -sha256 -hmac "your-hevy-webhook-secret" /tmp/payload.txt | sed 's/^.* //')

curl -X POST http://localhost:9090/import/csv \
-H "Content-Type: multipart/form-data; boundary=----Boundary123" \
-H "X-Hevy-Signature: $SIGNATURE" \
--data-binary @/tmp/payload.txt
-H "X-API-Key: your-rest-api-key" \
-F "file=@hevy_export.csv"
```

> **Note**: Most HTTP clients (including `curl -F`) construct multipart bodies dynamically, making it hard to pre-compute the signature. For programmatic access, build the multipart payload manually or use a client library that supports request signing.

---

### Importing Hevy CSV Data

Export your workout data from the Hevy app and use the REST API endpoint to import:

```bash
# Without signature verification (HEVY_WEBHOOK_SECRET not set)
# Without API key authentication (REST_API_KEY not set)
curl -X POST http://localhost:9090/import/csv \
-F "file=@hevy_export.csv"
```

When `HEVY_WEBHOOK_SECRET` is configured, include the `X-Hevy-Signature` header as described in [API Authentication](#api-authentication-signature-verification).
When `REST_API_KEY` is configured, include the `X-API-Key` header as described in [API Authentication](#api-authentication-api-key).

---

Expand All @@ -261,11 +235,11 @@ Add the following to your `.env` file:

```bash
HEVY_API_KEY=your-hevy-api-key
HEVY_WEBHOOK_SECRET=your-hevy-webhook-secret
REST_API_KEY=your-rest-api-key
```

- `HEVY_API_KEY`: Your personal Hevy API key (available in Hevy app settings). Required for the server to fetch workout details when a webhook is received.
- `HEVY_WEBHOOK_SECRET`: A shared secret string that you and Hevy agree on to verify webhook authenticity. **Keep this secret secure** — anyone with this secret can forge webhook requests. When set, it applies to **all** REST API endpoints (not just webhooks).
- `REST_API_KEY`: A static API key for REST API authentication. **Keep this secret secure** — anyone with this key can make API requests. When set, all REST API endpoints require the `X-API-Key` header with this exact value.

#### 2. Configure the Webhook URL in Hevy

Expand All @@ -286,7 +260,7 @@ When a workout is created or updated, Hevy sends a `POST` request to `/webhooks/
| Header | Value | Description |
|--------|-------|-------------|
| `Content-Type` | `application/json` | JSON payload |
| `X-Hevy-Signature` | `<hex>` | HMAC-SHA256 hex digest of the raw request body, using `HEVY_WEBHOOK_SECRET` as the key. **Required when `HEVY_WEBHOOK_SECRET` is configured.** |
| `X-API-Key` | `<string>` | Static API key matching the `REST_API_KEY` value. **Required when `REST_API_KEY` is configured.** |

**Body:**

Expand All @@ -301,14 +275,14 @@ When a workout is created or updated, Hevy sends a `POST` request to `/webhooks/
```mermaid
flowchart LR
H[Hevy App] -->|POST /webhooks/hevy| W[Workout MCP Server]
W -->|Verify Signature| V{Valid?}
W -->|Verify API Key| V{Valid?}
V -->|No| R1[400 Bad Request]
V -->|Yes| P[Queue Background Task]
P --> F[Fetch Workout from Hevy API]
F --> U[Upsert into Database]
```

- The endpoint responds immediately with `{"status": "ok"}` (HTTP 200) after signature verification.
- The endpoint responds immediately with `{"status": "ok"}` (HTTP 200) after API key verification.
- The actual workout fetch and database upsert happen asynchronously in a background task.
- If `HEVY_API_KEY` is not configured, the endpoint returns `503 Service Unavailable`.

Expand Down Expand Up @@ -388,7 +362,7 @@ All settings are managed via `pydantic-settings` with `.env` file auto-loading.
| `APP_PORT` | `9090` | Port for the REST API server |
| `MCP_PORT` | `9091` | Port for the MCP server |
| `HEVY_API_KEY` | `None` | Hevy API key (required for webhook sync and automatic fetching) |
| `HEVY_WEBHOOK_SECRET` | `None` | Shared secret for verifying Hevy webhook signatures |
| `REST_API_KEY` | `None` | Static API key for REST API authentication |
| `HEVY_BASE_URL` | `https://api.hevyapp.com` | Hevy API base URL |
| `LOG_LEVEL` | `INFO` | Logging level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
| `LOG_FORMAT` | `console` | Log output format (`console` for dev, `json` for production) |
Expand Down
Loading
Loading