Event Envelope
Every event emitted by HealthSync uses this envelope. The envelope handles classification, provenance, and delivery routing. Data items inside data[] are self-contained with their own identity, timestamps, and versioning.
Webhook Signing
Section titled “Webhook Signing”HealthSync signs every outbound webhook POST with the tenant’s webhook signing secret (wss_...). Verify the signature before parsing or trusting the envelope.
| Header | Value |
|---|---|
X-HealthSync-Signature-256 |
sha256=<hex>, where <hex> is the lowercase HMAC-SHA256 digest |
X-HealthSync-Timestamp |
Unix timestamp in seconds |
X-HealthSync-Delivery-Id |
Stable delivery id for this envelope — identical on every redelivery or retry of the same logical delivery |
X-HealthSync-Event-Type |
Canonical event_type string |
To verify a delivery:
- Read the exact raw UTF-8 request body. Do not parse and re-stringify JSON before verifying.
- Reject the request if
X-HealthSync-Timestampis missing, invalid, or more than 5 minutes from your current clock. - Build the signing input exactly as
{timestamp}.{deliveryId}.{body}. The two dots are literal separators. - Compute
HMAC-SHA256using the plaintext webhook signing secret as UTF-8 bytes. - Lowercase-hex encode the digest, prefix it with
sha256=, and compare it withX-HealthSync-Signature-256using a constant-time comparison. - Deduplicate accepted at-least-once redeliveries on
X-HealthSync-Delivery-Id.
import crypto from "node:crypto";
export function verifyHealthSyncWebhook(input: { rawBody: string; headers: Headers; webhookSigningSecret: string; toleranceSeconds?: number;}): boolean { const timestamp = input.headers.get("X-HealthSync-Timestamp"); const deliveryId = input.headers.get("X-HealthSync-Delivery-Id"); const signature = input.headers.get("X-HealthSync-Signature-256"); if (!timestamp || !deliveryId || !signature) return false;
const timestampSeconds = Number(timestamp); if (!Number.isInteger(timestampSeconds)) return false;
const tolerance = input.toleranceSeconds ?? 300; const nowSeconds = Math.floor(Date.now() / 1000); if (Math.abs(nowSeconds - timestampSeconds) > tolerance) return false;
const signingInput = `${timestamp}.${deliveryId}.${input.rawBody}`; const expectedDigest = crypto .createHmac("sha256", Buffer.from(input.webhookSigningSecret, "utf8")) .update(signingInput, "utf8") .digest("hex"); const expected = `sha256=${expectedDigest}`;
const expectedBytes = Buffer.from(expected, "utf8"); const actualBytes = Buffer.from(signature, "utf8"); return ( expectedBytes.length === actualBytes.length && crypto.timingSafeEqual(expectedBytes, actualBytes) );}Structure
Section titled “Structure”{ "event_id": "550e8400-e29b-41d4-a716-446655440000", "schema_version": "1.1", "event_type": "activity", "stream": "session",
"provider": "garmin", "sources": [ { "provider_notification_id": "notif-abc123", "fetched_at": 1742585000000 } ], "devices": [ { "device_id": "dev-001", "name": "Forerunner 265", "model": "3991", "provider_device_id": "dev-001" } ],
"tenant_id": "tenant-uuid", "user_id": "user-uuid", "external_user_ref": "your-user-123", "connection_id": "conn-uuid",
"emitted_at": 1742585400000,
"data": [ // array of type-specific data items ]}Field Reference
Section titled “Field Reference”| Field | Type | Description |
|---|---|---|
event_id |
string (uuid) |
Unique per emission. Re-processing the same data produces a new event_id. |
schema_version |
string |
Envelope schema version for forward compatibility. |
event_type |
string |
Discriminator for the data shape. All items in data[] share this type. |
stream |
string |
Stream tier: "session", "snapshot", "timeseries", "aggregate", or "profile". |
provider |
string |
Source provider identifier (e.g., "garmin", "googleHealth", or "polar"). |
sources |
array |
Notification references that triggered this event. Empty [] for backfill or non-notification events. |
sources[].provider_notification_id |
string | null |
The provider notification that triggered the fetch. |
sources[].fetched_at |
number (epoch ms) |
When HealthSync fetched in response to the notification. |
devices |
array |
Device registry for this event. Data items reference devices by device_id. |
devices[].device_id |
string |
HealthSync-scoped device identifier. |
devices[].name |
string | null |
Device display name. |
devices[].model |
string | null |
Device model identifier. |
devices[].provider_device_id |
string | null |
Provider’s native device ID. |
tenant_id |
string |
Target tenant. |
user_id |
string |
Target user within the tenant (HealthSync internal tenant_user_id). |
external_user_ref |
string |
Tenant-provided external user reference echoed from connect-time user creation. |
connection_id |
string |
The connection that produced this data. |
emitted_at |
number (epoch ms) |
When HealthSync emitted this event. |
data |
array |
One or more data items, all of the same event_type. |
Sources
Section titled “Sources”The sources array records which provider notifications triggered this event:
- Notification-triggered events: One or more source entries with
fetched_attimestamps. - Backfill / replay events: Empty array
[]. - Manual / scheduled fetches: Empty array
[].
Devices
Section titled “Devices”The devices array is a registry of devices relevant to this event. Individual data items reference devices by device_id. When device attribution is unavailable, a data item sets device_id to null.