Security and data handling
HC Sync stores health values as plain JSON in Postgres and uses HTTPS for API traffic. Secret keys are stored as SHA-256 hashes; signing secrets remain readable to the server. Health records expire 13 months after their start time. This page describes implemented controls and identifies provider settings the code cannot prove.
Last updated: 2026-11-04. Source for the 2026 service details: HC Sync page details.
What data is stored?
| Table | Fields |
|---|---|
hc_projects | id, name, SHA-256 hash of the secret key, a random token secret (used to sign user tokens and read keys), plan, webhook URL, webhook signing secret, billing subscription id, email (optional), created_at |
hc_records | project_id, user_id (the id you assign), record id, type, start and end time, value (JSON, up to 64 KB), source app, received_at. This is the health data. |
hc_user_gen | project_id, user_id, a deletion counter. Kept after a user is deleted so old device tokens stay revoked. |
hc_outbox | Webhook events that failed to deliver, including the record payload, until delivered or 7 days old. Up to 500 per project. |
hc_deliveries | Webhook attempt log: event type, event id, success, duration. No health values. |
hc_uptime, hc_hits, hc_leads | Uptime samples; page views (path and referring hostname only, no IP or user agent); contact-form entries (email, app link, answers, note). |
Health values are stored as plain JSON in the database. The application does not add its own encryption layer on top of the database's.
Where does HC Sync run?
- API: Vercel serverless functions, pinned to region
iad1invercel.json. - Database: Postgres reached through
DATABASE_URL; the code is written for Supabase and pins Supabase's root CA. The database region and encryption at rest are provider settings that are not in this repository, so this page cannot prove them. Our privacy page states AWS us-east-1, encrypted at rest.
How is data encrypted in transit?
- The database connection uses TLS with certificate verification on (
rejectUnauthorized: true) against the pinned Supabase root certificate. - Browser and device traffic to the API is served over HTTPS by Vercel. The code itself does not enforce HTTPS; that is Vercel's platform behavior.
- Webhook URLs must be
https. Delivery refuses private, loopback, link-local and other special-purpose addresses, resolves the host once and connects to exactly that address. - Responses carry a strict Content-Security-Policy,
X-Frame-Options: DENYandnosniff.
How do keys and tokens work?
- Secret key (
hcs_secret_…): 24 random bytes, shown once. Only its SHA-256 hash is stored, so we cannot show it again. It can do everything, including rotation and deletion. Keep it on your server. - Read key (
hcs_read_…): reads records, summaries, users, project info and webhook delivery logs; cannot write, rotate, delete or change the webhook. It is an HMAC of the project id under the token secret, not stored, and checked in constant time. Give it to dashboards and MCP clients. - User token (
hcs_user_…): minted by your server for one user id; an HMAC over project, user and a deletion counter. It can only upload and delete records for that one user. Deleting the user invalidates every earlier token for that user. - Rotation:
POST /v1/keys/rotateissues a new secret key, token secret and webhook secret; all read keys and user tokens stop working at once. - The token secret and webhook secret are stored in the database as readable values, because the server needs them to sign. Only the secret key is stored as a hash.
How are webhooks signed?
Every delivery carries x-hcsync-timestamp and x-hcsync-signature: hex HMAC-SHA256 of {timestamp}.{raw body} with your webhook secret. Retries are signed again, so they pass a 5-minute freshness check. Each event has a unique id for de-duplication. See the docs for verification code.
How long is each data type retained?
A daily job (03:30 UTC) and a 5-minute job apply these windows:
| Data | Kept |
|---|---|
| Health records | Deleted once their start time is more than 13 months old (not 13 months from upload) |
Sample data (sample_user) | Deleted 7 days after upload |
| Queued webhook events and delivery log | 7 days |
| Uptime samples | 90 days |
| Page-view counts | 180 days |
| Project row, contact-form entries | Until you delete the project; contact-form entries have no automatic expiry in code (deleted on request) |
Database backups and their retention are a provider setting and are not described in this repository.
How does deletion work?
DELETE /v1/users/{id}(secret key): erases that user's records, drops their queued webhook events and revokes their device tokens immediately. A deletion that races an in-flight upload wins. The deletion counter row remains.POST /v1/sync/delete(user token): the device removes specific records.DELETE /v1/project(secret key): deletes the project; records, queued events, delivery log and counters cascade.- Your webhook may receive
records.deletedanduser.deletedevents. Signed events with ids for dedupe. Queued events retry in order every 5 minutes, on sync or replay, for up to 7 days. The queue holds 500 events per project; new events are discarded when full. Record writes and event queuing are separate, so a failure between them can lose an event. REST exposes current records, but cannot recover missed deletion events.
What limits apply?
| Plan | Synced users per calendar month (UTC) | Stored records |
|---|---|---|
| free | 5 | 200,000 |
| starter | 100 | no cap in code |
| indie | 1,000 | no cap in code |
| studio | 20,000 | no cap in code |
Also enforced: at most 1,000 records per sync call, values under 64 KB, and 500 queued webhook events per project. Anonymous project creation is limited to 20 requests per IP per hour; contact submissions to 10. Counters use hashed IPs in the database. These endpoints return 429 with Retry-After when limited. Other endpoints have no application rate limiter.
Which providers does the code use?
- Vercel: hosting and functions.
- Supabase: Postgres database (the code is written for it; the pinned certificate is Supabase's).
- Lemon Squeezy: billing. The code handles its signed subscription webhooks; it never receives health data.
What does the server log?
Unexpected errors log the error code and message only, never the query or its values.
Which security features are unavailable?
- No HIPAA Business Associate Agreement. Do not send protected health information that requires one.
- No SOC 2 report or other third-party audit.
- No customer-managed encryption keys, no IP allow-listing, no per-user audit log of reads.
- No regions other than those above.
How do you report a security problem?
To report a vulnerability or ask a security question, email shaharfrish6@gmail.com.
Describes the code as of 31 October 2026.
What else should you know?
Does the application encrypt health values itself?
Health values are stored as plain JSON in the database. The application does not add its own encryption layer on top of the database's.
Is there an independent audit report?
No SOC 2 report or other third-party audit.
How long is sample data kept?
Sample data is deleted 7 days after upload.