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 String

The identifier for the user's profile associated with this data log.

accountId String

The identifier for the account associated with this data log.

externalId String

An external identifier for the data log, if applicable.

receivedAtUtc String

The timestamp when the data log was received in UTC. In ISO 8601 format.

logType String

The type of log (e.g., Activity, Blood, Body, Device, Energy, Heart, Oxygen, Sleep, Temperature).

dataType String

The specific type of data within the log.

dataLogs Array

An array of data logs.

dataLogs Properties

id String

A unique identifier for each data log.

parentId String

The identifier for the parent log, if applicable.

value Number

The recorded value for the data type.

unit String

The unit of measurement for the value.

source String

Identifies where or what device the data came from.

recordingMethod String

Describes how the data was collected (e.g., RECORDING_METHOD_ACTIVELY_RECORDED, RECORDING_METHOD_AUTOMATICALLY_RECORDED, RECORDING_METHOD_MANUAL_ENTRY, RECORDING_METHOD_UNKNOWN).

deviceType String

The specific type or model of the device that recorded the data.

startDateTime String

When the data recording started, in an ISO 8601 format.

endDateTime String

When the data recording ended, also in an ISO 8601 format.

additionalProperties Object

Additional 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 String

A unique identifier for each biomarker reading.

profileId String

The identifier for the user's profile associated with this biomarker.

accountId String

The identifier for the account associated with this biomarker.

externalId String

External identifier for the profile.

category String

The category of the biomarker, such as activity, sleep, or body.

type String

The specific type of biomarker.

periodicity String

The frequency at which the biomarker data is generated.

aggregation String

The method used to aggregate the biomarker data.

value String

The numerical value of the biomarker.

unit String

The unit of measurement for the biomarker value.

valueType String

The type of value, indicating whether it's an integer, float, etc.

startDateTime DateTimeOffset

The starting timestamp for the period over which the biomarker was calculated. In ISO 8601 format with a timezone offset.

endDateTime DateTimeOffset

The ending timestamp for the period over which the biomarker was calculated. In ISO 8601 format with a timezone offset.

createdAtUtc DateTimeOffset

The timestamp when the event was created in UTC. In ISO 8601 format.

version Double

The 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 string

A unique identifier for each health score.

profileId string

The identifier for the user's profile associated with this score.

accountId string

The identifier for the account associated with this score.

externalId string

External identifier for the profile.

type string

The type of health score (e.g., Wellbeing, Activity, Sleep, Depression, Stress, Anxiety).

state string

A qualitative assessment of the score (e.g., low, medium, high).

score number

A numerical value representing the health score.

factors array

A list of factors that influenced the score, each with a name and value.

dataSources array

The types of data used to calculate the score.

scoreDateTime string

The timestamp the score was calculated for. In ISO 8601 format in the profiles timezone offset.

createdAtUtc string

The timestamp when the score was calculated. In ISO 8601 format with a UTC timezone offset.

version number

The 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();
}
Previous
Quickstart