Guides
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.
ActivityLog_Received
dataType
Stringvalue
DoubleThe amount of times the data type occurred within the time period.
unit
StringThe unit of measure for the value.
source
StringSource of this data log. e.g. 'Iphone X', 'Walking Buddy'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
startDateTime
DateTimeOffsetDenotes the start date and time for the log period. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetDenotes the end date and time for the log period. In ISO 8601 format with a timezone offset.
[ { "DataType": "", "Value": 55, "Unit": 55, "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "StartDateTime": "2022-01-01T00:00:00+12:00", "EndDateTime": "2022-01-01T00:00:00+12:00" }]
BloodLog_Received
dataType
StringThe type of data being logged. Can be 'BloodGlucose', 'BloodPressureDiastolic', 'BloodPressureSystolic'
count
DoubleThe value corresponding to the data type.
unit
StringUnit the data is in, eg mg/dL (milligrams per deciliter) or mmol/L (millimoles per liter)
relationToMeal
StringRelationship to when the last or next meal is.
bodyPosition
StringThe position the body was in at time of measurement. E.g. sitting_down
measurementLocation
StringWhere the blood pressure was measured from. E.g. left_upper_arm
specimenSource
StringWhere the specimen was collected.
mealType
StringThe type of meal had prior to sampling.
source
StringSource of this data log. e.g. 'Iphone X'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
startDateTime
DateTimeOffsetDenotes the start date and time for the blood log period. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetDenotes the end date and time for the blood log period. In ISO 8601 format with a timezone offset.
[ { "DataType": "BloodGlucose", "Count": 55, "Unit": "mg/dL", "RelationToMeal": "Before Meal", "BodyPosition": "Body Position", "MeasurementLocation": "Measurement Location", "SpecimenSource": "capillary_blood", "MealType": "breakfast", "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "StartDateTime": "2022-01-01T00:00:00+12:00", "EndDateTime": "2022-01-01T00:00:00+12:00" }]
BodyLog_Received
dataType
Stringvalue
DoubleThe amount of times the data type occurred within the time period.
unit
StringThe unit of measure for the value.
source
StringSource of this data log. e.g. 'Iphone X', 'Walking Buddy'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
startDateTime
DateTimeOffsetDenotes the start date and time for the log period. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetDenotes the end date and time for the log period. In ISO 8601 format with a timezone offset.
[ { "DataType": "", "Value": 55, "Unit": 55, "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "StartDateTime": "2022-01-01T00:00:00+12:00", "EndDateTime": "2022-01-01T00:00:00+12:00" }]
Insight_Received
name
StringThe name of the insight.
value
Double (nullable)The value of the insight, this is the quantity of the unit.
unit
StringThe unit the value is measured in.
startDateTime
DateTimeOffset (nullable)The starting point for the insight window (If applicable).
endDateTime
DateTimeOffset (nullable)The ending point for the insight window (If applicable).
[ { "Name": "TotalDailySteps", "Value": 1000, "Unit": "Count", "StartDateTime": "2022-01-01T00:00:00+12:00", "EndDateTime": "2022-01-02T00:00:00+12:00" }]
DeviceLog_Received
isLocked
BooleanDenotes whether the users device is locked or unlocked at the time specified.
isScreenOn
BooleanDenotes whether the users device screen is on at the time specified. This distinguishes between a user waking a phone or fully unlocking it.
source
StringSource of this data log. e.g. 'Iphone X'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
eventTimeStamp
DateTimeOffsetDate and time the event occurred. In ISO 8601 format with a timezone offset.
[ { "IsLocked": true, "IsScreenOn": false, "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "EventTimestamp": "2022-01-01T00:00:00+12:00" }]
EnergyLog_Received
dataType
Stringvalue
DoubleThe amount of times the data type occurred within the time period.
unit
StringThe unit of measure for the value.
source
StringSource of this data log. e.g. 'Iphone X', 'Walking Buddy'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
startDateTime
DateTimeOffsetDenotes the start date and time for the log period. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetDenotes the end date and time for the log period. In ISO 8601 format with a timezone offset.
[ { "DataType": "", "Value": 55, "Unit": 55, "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "StartDateTime": "2022-01-01T00:00:00+12:00", "EndDateTime": "2022-01-01T00:00:00+12:00" }]
HeartLog_Received
dataType
StringThe type of data being logged. Can be 'HeartRate', 'RestingHeartRate', 'HeartRateVariablity'
count
DoubleThe value corresponding to the data type.
source
StringSource of this data log. e.g. 'Iphone X'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
startDateTime
DateTimeOffsetDenotes the start date and time for the heart log period. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetDenotes the end date and time for the heart log period. In ISO 8601 format with a timezone offset.
[ { "DataType": "HeartRate", "Count": 55, "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "StartDateTime": "2022-01-01T00:00:00+12:00", "EndDateTime": "2022-01-01T00:00:00+12:00" }]
Inference_Created
id
GuidUnique identifier for the inference.
type
StringSpecifies the type of score being reported, such as stress_resilience or depression_resilience. The type informs the client about which aspect of health the inference relates to.
state
StringIndicates the category of the score in a descriptive manner. Categories include none, low, medium, or high. This field provides a qualitative summary of the corresponding score.
score
DoubleThe numerical value of the health score, ranging between 0 and 1. Higher scores typically indicate better mental and physical well-being based on the features analyzed.
factors
List`1A list of objects that break down the score contributions of different features such as sleep_routine, daily_activity, etc. Each object in the list specifies how much a particular feature has contributed to the final score.
inputData
String[]Lists the features that were used to generate the inference. This can include features like age, gender, sleep, and activity.
createdAt
DateTimeOffsetDate the inference was generated.
{ "Id": "2F9EA4F8-8A96-4AC2-88B5-001EE6FC2CCD", "Type": "stress_resilience", "State": "low", "Score": "0.88", "Factors": [ { "name": "sleep_routine", "value": "0.12" } ], "InputData": [ "age", "sleep", "activity" ], "CreatedAt": "2023-01-01 00:00:00.0000000 +00:00"}
MovementLog_Received
dataType
StringThe type of movement being logged. Can be 'StepCount', 'FloorsDescended', 'FloorsAscended', 'Pace'
count
DoubleThe amount of times the data type occurred within the time period.
source
StringSource of this data log. e.g. 'Iphone X', 'Walking Buddy'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
startDateTime
DateTimeOffsetDenotes the start date and time for the movement log period. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetDenotes the end date and time for the movement log period. In ISO 8601 format with a timezone offset.
[ { "DataType": "StepCount", "Count": 55, "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "StartDateTime": "2022-01-01T00:00:00+12:00", "EndDateTime": "2022-01-01T00:00:00+12:00" }]
OxygenLog_Received
dataType
Stringvalue
DoubleThe amount of times the data type occurred within the time period.
unit
StringThe unit of measure for the value.
source
StringSource of this data log. e.g. 'Iphone X', 'Walking Buddy'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
startDateTime
DateTimeOffsetDenotes the start date and time for the log period. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetDenotes the end date and time for the log period. In ISO 8601 format with a timezone offset.
[ { "DataType": "", "Value": 55, "Unit": 55, "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "StartDateTime": "2022-01-01T00:00:00+12:00", "EndDateTime": "2022-01-01T00:00:00+12:00" }]
SleepLog_Received
durationInMinutes
DoubleThe Length of time in minutes the user was asleep for the given period.
sleepStage
StringThe stage of sleep the user was in. Can be one of 'in bed', 'asleep', 'awake'.
source
StringSource of this data log. e.g. 'Iphone X', 'Walking Buddy'
recordingMethod
StringThe method used to record the log: RECORDING_METHOD_ACTIVELY_RECORDED (For actively recorded data by the user), RECORDING_METHOD_AUTOMATICALLY_RECORDED (For passively recorded data by the app), RECORDING_METHOD_MANUAL_ENTRY (For manually entered data by the user), RECORDING_METHOD_UNKNOWN (Unknown recording method).
deviceType
StringThe device which recorded the log.
startDateTime
DateTimeOffsetDenotes the start date and time for the sleep period. In ISO 8601 format with a timezone offset.
endDateTime
DateTimeOffsetDenotes the end date and time for the sleep period. In ISO 8601 format with a timezone offset.
[ { "DurationInMinutes": 60, "SleepStage": "asleep", "Source": "Iphone X", "RecordingMethod": "RECORDING_METHOD_UNKNOWN", "DeviceType": "iPhone13,2", "StartDateTime": "2022-07-01T10:00:00+09:00", "EndDateTime": "2022-07-01T11:00:00+09:00" }]
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 "BloodLog_Received": var bloodLog = JsonConvert.DeserializeObject<List<BloodLogDto>>(payload); break; case "HeartLog_Received": var heartLog = JsonConvert.DeserializeObject<List<HeartLogDto>>(payload); break; case "DeviceLog_Received": var deviceLog = JsonConvert.DeserializeObject<List<DeviceLogDto>>(payload); break; case "MovementLog_Received": var movementLog = JsonConvert.DeserializeObject<List<MovementLogDto>>(payload); break; case "SleepLog_Received": var sleepLog = JsonConvert.DeserializeObject<List<SleepLogDto>>(payload); break; case "Inference_Created": var inference = JsonConvert.DeserializeObject<InferenceDto>(payload); break; // Process other Events default: return BadRequest($"Unsupported event type: {eventTypeHeader}"); }
// Return Ok so we know to not retry. return Ok();}