Data Delivery
Introduction to Webhooks
Sahha processes millions of rows of log data with the primary objective of producing insightful inference results. If needed, we have the capacity to forward the data events we obtain to a selected endpoint of your choice.
Webhooks are the preferred approach for accessing Sahha data
Webhooks offer a real-time, efficient, and customizable method for accessing Sahha data, allowing you to only receive the specific event data you need. Enhancing data processing efficiency, security, and cost-effectiveness.
How can I use webhooks?
Webhooks can be setup within the Sahha Dashboard under the webhooks tab on the side-nav
Security
To know for sure that a webhook was, in fact, sent by Sahha instead of a malicious actor, you can verify the request signature. Each webhook request contains a header named X-Signature
, and you can verify this signature by using your secret webhook key. The signature is an HMAC hash of the request payload hashed using your secret key.
If your generated signature matches the X-Signature header
, you can be sure that the request was coming from Sahha.
Identifying profiles
Each payload has a header X-External-Id
which contains the external identifier provided by you when registering your profile, this will allow you to identify the profile the events are related to.
Identifying events
In order to determine the appropriate event type to deserialize the payload into, you should examine the value present in the X-Event-Type
header.
Events
Below are the events that will be fired to your elected URL, please review the code sample above for how to consume these correctly.
Real-Time Health Logs
X-Event-Type - DataLogReceivedIntegrationEvent
profileId
StringThe identifier for the user's profile associated with this data log.
accountId
StringThe identifier for the account associated with this data log.
externalId
StringAn external identifier for the data log, if applicable.
receivedAtUtc
StringThe timestamp when the data log was received in UTC. In ISO 8601 format.
logType
StringThe type of log (e.g., Activity, Blood, Body, Device, Energy, Heart, Oxygen, Sleep, Temperature).
dataType
StringThe specific type of data within the log.
dataLogs
ArrayAn array of data logs.
dataLogs Properties
id
StringA unique identifier for each data log.
parentId
StringThe identifier for the parent log, if applicable.
value
NumberThe recorded value for the data type.
unit
StringThe unit of measurement for the value.
source
StringIdentifies where or what device the data came from.
recordingMethod
StringDescribes how the data was collected (e.g., RECORDING_METHOD_ACTIVELY_RECORDED, RECORDING_METHOD_AUTOMATICALLY_RECORDED, RECORDING_METHOD_MANUAL_ENTRY, RECORDING_METHOD_UNKNOWN).
deviceType
StringThe specific type or model of the device that recorded the data.
startDateTime
StringWhen the data recording started, in an ISO 8601 format.
endDateTime
StringWhen the data recording ended, also in an ISO 8601 format.
additionalProperties
ObjectAdditional properties associated with the data log.
[ { "logType": "activity", "dataType": "steps", "profileId": "123e4567-e89b-12d3-a456-426614174001", "accountId": "123e4567-e89b-12d3-a456-426614174002", "externalId": "ext-789", "receivedAtUtc": "2023-06-26T12:34:56+00:00", "dataLogs": [ { "id": "123e4567-e89b-12d3-a456-426614174003", "parentId": null, "value": 10000, "unit": "count", "source": "iPhone X", "recordingMethod": "RECORDING_METHOD_AUTOMATICALLY_RECORDED", "deviceType": "iPhone13,2", "startDateTime": "2023-06-25T00:00:00+00:00", "endDateTime": "2023-06-25T23:59:59+00:00", "additionalProperties": { "context": "morning_walk" } } ] }]
Digital Biomarker Logs
X-Event-Type - BiomarkerCreatedIntegrationEvent
id
StringA unique identifier for each biomarker reading.
profileId
StringThe identifier for the user's profile associated with this biomarker.
accountId
StringThe identifier for the account associated with this biomarker.
externalId
StringExternal identifier for the profile.
category
StringThe category of the biomarker, such as activity, sleep, or body.
type
StringThe specific type of biomarker.
periodicity
StringThe frequency at which the biomarker data is generated.
aggregation
StringThe method used to aggregate the biomarker data.
value
StringThe numerical value of the biomarker.
unit
StringThe unit of measurement for the biomarker value.
valueType
StringThe type of value, indicating whether it's an integer, float, etc.
startDateTime
DateTimeOffsetThe starting timestamp for the period over which the biomarker was calculated. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetThe ending timestamp for the period over which the biomarker was calculated. In ISO 8601 format with a timezone offset.
createdAtUtc
DateTimeOffsetThe timestamp when the event was created in UTC. In ISO 8601 format.
version
DoubleThe version of the event schema.
{ "id": "123e4567-e89b-12d3-a456-426614174000", "profileId": "123e4567-e89b-12d3-a456-426614174001", "accountId": "123e4567-e89b-12d3-a456-426614174002", "externalId": "ext-789", "category": "activity", "type": "steps", "periodicity": "daily", "aggregation": "total", "value": "10000", "unit": "count", "valueType": "integer", "startDateTime": "2023-06-25T00:00:00+00:00", "endDateTime": "2023-06-25T23:59:59+00:00", "createdAtUtc": "2023-06-26T12:34:56+00:00", "version": 1}
Health Scores
X-Event-Type - ScoreCreatedIntegrationEvent
id
stringA unique identifier for each health score.
profileId
stringThe identifier for the user's profile associated with this score.
accountId
stringThe identifier for the account associated with this score.
externalId
stringExternal identifier for the profile.
type
stringThe type of health score (e.g., Wellbeing, Activity, Sleep, Depression, Stress, Anxiety).
state
stringA qualitative assessment of the score (e.g., low, medium, high).
score
numberA numerical value representing the health score.
factors
arrayA list of factors that influenced the score, each with a name and value.
dataSources
arrayThe types of data used to calculate the score.
scoreDateTime
stringThe timestamp the score was calculated for. In ISO 8601 format in the profiles timezone offset.
createdAtUtc
stringThe timestamp when the score was calculated. In ISO 8601 format with a UTC timezone offset.
version
numberThe version of the event schema.
{ "id": "123e4567-e89b-12d3-a456-426614174000", "profileId": "123e4567-e89b-12d3-a456-426614174001", "accountId": "123e4567-e89b-12d3-a456-426614174002", "externalId": "ext-789", "type": "activity", "state": "medium", "score": 73.5, "factors": [ { "name": "steps", "value": 10000, "goal": 12000, "score": 0.8, "state": "medium", "unit": "count" }, { "name": "active_calories", "value": 74, "goal": 500, "score": 0.19, "state": "low", "unit": "kcal" }, { "name": "active_hours", "value": 8, "goal": 10, "score": 0.85, "state": "medium", "unit": "hour" }, { "name": "extended_inactivity", "value": 925, "goal": 480, "score": 0.95, "state": "high", "unit": "minute" }, { "name": "intense_activity_duration", "value": 1, "goal": 30, "score": 0.37, "state": "minimal", "unit": "minute" }, { "name": "floors_climbed", "value": 27, "goal": 10, "score": 1, "state": "high", "unit": "count" } ], "dataSources": [ "activity" ], "createdAtUtc": "2023-06-26T12:34:56+00:00", "version": 1}
Handler examples
[HttpPost("receiver")]public async Task<IActionResult> ProcessWebhook(){ var signatureHeader = Request.Headers["X-Signature"].ToString(); if (string.IsNullOrEmpty(signatureHeader)) { return BadRequest("X-Signature header is missing."); }
var externalIdHeader = Request.Headers["X-External-Id"].ToString(); if (string.IsNullOrEmpty(externalIdHeader)) { return BadRequest("X-External-Id header is missing."); }
var eventTypeHeader = Request.Headers["X-Event-Type"].ToString(); if (string.IsNullOrEmpty(eventTypeHeader)) { return BadRequest("X-Event-Type header is missing."); }
// Read the request payload var payload = await new StreamReader(Request.Body).ReadToEndAsync();
// Compute the signature based on the provided secret var secretKey = "p+UnYW0MpNSonDdnr3sDH/ZRCOxs1etrjV57wC8yQWA="; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey)); var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)); var signatureString = BitConverter.ToString(computedHash).Replace("-", "").ToLower();
// Compare the received signature with the expected signature if (!string.Equals(signatureHeader, signatureString, StringComparison.OrdinalIgnoreCase)) { return BadRequest("Invalid signature."); }
// Deserialize the payload based on the event type switch (eventTypeHeader) { case "DataLogReceivedIntegrationEvent": // Handle Data Log Event break; case "BiomarkerCreatedIntegrationEvent": // Handle Biomarker Event break; case "ScoreCreatedIntegrationEvent": // Handle Score Event break; default: return BadRequest($"Unsupported event type: {eventTypeHeader}"); }
// Return Ok so we know to not retry. return Ok();}