Webhooks

Webhooks are a way to receive new data from Vital. We frequently poll to receive data from the various providers. By registering webhooks you can avoid polling our servers for data. We have several webhook event types that you can subsrcibe to.

Webhook Event

All our webhook events are of the following structure. Each event consists of a event_type and event_code followed by a payload. The event_type specifies the type of data that is being sent and the event_code specifies the type of event that caused this webhook to be triggered.
Webhook Events
1
2
3
4
5
{
 "event_type": "workouts",
 "data": "...event_specific_data",
 "event_code": "HISTORICAL_DATA_UPDATE"
}

Webhook Event Codes

We currently send the following event codes.
  • HISTORICAL_DATA_UPDATE is sent when the initial connection is made to Vital and we have pulled the historical information. We may send multiple of these events as we backfill data from the provider.
  • UPDATED is sent when an existing data object was updated. I.e. if a workout was later changed this event will be sent.
  • CREATED is sent when a new data object is created, e.g. when an exercise is created.
  • ERROR is sent when a connected provider is in error state as either the password/username has been changed.
Webhook Event Codes
1
2
3
4
5
6
7
8
// Historical Data is now available
HISTORICAL_DATA_UPDATE = "HISTORICAL_DATA_UPDATE"
// Existing data has been updated
UPDATED = "UPDATED"
// New data has been created
CREATED = "CREATED"
// Error Message
ERROR = "ERROR"

Webhook Event Types

You can register webhooks for the following event types. For each event type we will issue webhook events if a webhook is registered.
Webhook Event Types
1
2
3
4
5
6
7
8
ACTIVITY = "activity"
SLEEP = "sleep"
BODY = "body"
WORKOUTS = "workouts"
CONNECTION_ERROR = "connection_error"
PROVIDER_SPECIFIC = "provider_specific"
TESTKIT_ORDERS = "testkit_orders"
VITALS_GLUCOSE = "vitals_glucose"

Registering Webhook

Webhooks

Webhooks can be used to receive data from users in realtime. This is recommended vs polling the api to receive data. To get started register a webhook by following the steps below. In order to verify that you are both the owner of the app and the webhook URL, Vital will perform a Challenge-Response Check (CRC), which is not to be confused with a cyclic redundancy check.
CRC check consists of the following steps:
1) Generate a verify_token and use this to register your webhook. By making the below request.
2) Vital API will make a GET request with the verify_token, challenge, and event_type in the url parameters, and will expect you to return the challenge token in the response in a timely fashion. Use the verify token to validate that the request was sent from the Vital API.
3) Your webhook is now registered.
Webhook Register
1
2
3
4
5
6
7
8
9
from vital import Client

client = Client(client_id, client_secret, "sandbox")
    
client.Webhooks.register(
        callback_url="https://<url>/webhook/workout", 
        verify_token="123456789", 
        event_type="workouts"
        )
Your Server
1
2
3
4
5
6
7
# Example using Fastapi

@app.get("/webhook/{event_type}", response_model=dict)
async def register_webhook(challenge: str, verify_token: str, event_type: str):
  if verify_token != "123456789":
    raise HTTPException(400, "Incorrect verify token")
  return {"challenge": challenge}

Webhook Payload Signing

Vital signs the webhook events it sends to your endpoints by including a signature in each event’s Vital-Signature header. This allows you to verify that the events were sent by Vital, not by a third party. You can verify signatures either using our official libraries, or manually using your own solution. This implementation is based on a similar implementation to webhook signing as outlined by the Stripe API, extracts from their documents have been copied for brevity.
Before you can verify signatures, you need to retrieve your webhook secret from your Dashboard’s Webhooks settings. Select an environment that you want to obtain the secret for, then click the Click to reveal button.
Webhook Payload verification
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Example using Fastapi
secret = "your_webhook_secret"

@app.post("/webhooks/{event_type}")
def receive_webhook_event(request: Request, event_type: str):
    payload = request.body
    signature = request.headers.get("X-VITAL-SIGNATURE")
    
    try:
        payload = client.Webhooks.construct_webhook_event(payload: payload, 
                                                    received_sig=signature, 
                                                    webhook_secret=secret)
        # Store payload/excute whatever is required
    except SignatureVerificationError:
        # Failed to pass signature header verification
        pass
        

Preventing Replay Attacks

A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, Vital includes a timestamp in the Vital-Signature header. Because this timestamp is part of the signed payload, it is also verified by the signature, so an attacker cannot change the timestamp without invalidating the signature. If the signature is valid but the timestamp is too old, you can have your application reject the payload. Our libraries have a default tolerance of five minutes between the timestamp and the current time. You can change this tolerance by providing an additional parameter when verifying signatures. Use Network Time Protocol (NTP) to ensure that your server’s clock is accurate and synchronizes with the time on Vital's servers. Vital generates the timestamp and signature each time an event is sent to your endpoint.

Verifying Signatures Manually

The Vital-Signature header included in each signed event contains a timestamp and one or more signatures. The timestamp is prefixed by t=, and each signature is prefixed by a scheme. Schemes start with v, followed by an integer. Currently, the only valid live signature scheme is v1.
Vital-Signature
1
2
3
Vital-Signature:
t=1592574557,
v1=5257a869e7ecebeda32afsadsadas51cad7e7dsadsadsa3221313sadsa1
Vital generates signatures using a hash-based message authentication code (HMAC) with SHA-256. To prevent downgrade attacks, you should ignore all schemes that are not v1. Although it’s recommended to use our official libraries to verify webhook event signatures, you can create a custom solution by following these steps.

Step 1: Extract the timestamp and signatures from the header

Split the header, using the , character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a prefix and value pair. The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature (or signatures). You can discard all other elements.

Step 2: Prepare the signed_payload string

The signed_payload string is created by concatenating: The timestamp (as a string) The character . The actual JSON payload (i.e., the request body)

Step 3: Determine the expected signature

Compute an HMAC with the SHA256 hash function. Use the endpoint’s signing secret as the key, and use the signed_payload string as the message.

Step 4: Compare the signatures

Compare the signature (or signatures) in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance. To protect against timing attacks, use a constant-time string comparison to compare the expected signature to each of the received signatures.

Webhook Management

Below are the endpoints to retrieve and delete existing webhooks.