Webhook notifications

Webhook notifications enable your application to receive real-time updates from Suunto about users’ activities. With this feature, you can integrate Suunto data into your system efficiently. Here's how to configure, verify, and handle webhook notifications.

Setting Up

  1. Configure Webhook URLs

    • Log in to your OAuth application settings at the profile -page.

    • Configure the notification URLs where webhook requests should be sent.

  2. Set a Notification Secret

    • Define a notification secret in your app settings.

    • Store the notification secret securely so it's available to the service handling the webhook requests.

Types of Notifications

Suunto sends three types of webhook notifications:

  • New Workout: Triggered when a user uploads a new workout.

  • New Route: Triggered when a user creates a new route.

  • 24/7 Activity Data: Triggered when a user uploads 24/7 activity samples (e.g., heart rate, step count).

  • 24/7 Sleep Data: Triggered when a user uploads 24/7 sleep samples (e.g., sleep duration, hrv).

  • 24/7 Recovery Data: Triggered when a user uploads 24/7 recovery samples (e.g., balance, stress).

The type of notification is specified in the type field of the request body or form parameters.

Webhook Request Examples

New Workout Notification

Content-Type: application/json

{ 
"type": "WORKOUT_CREATED",
"username": "johndoe123",
"workout": {
"workoutKey": "67604889401b942184624cb8",
"activityId": 3,
"startTime": 1702729200000,
"totalTime": 3600,
"energyConsumption": 500,
"startPosition": {
"x": 24.97242731,
"y": 60.27185791
},
"stepCount": 8500,
"totalAscent": 120.5,
"totalDescent": 110.3,
"totalDistance": 10000,
"hrdata": {
"workoutAvgHR": 145,
"workoutMaxHR": 175
},
"avgSpeed": 2.78,
"maxSpeed": 6,
"timeOffsetInMinutes": 120
},
"gear": {
"manufacturer": "Suunto",
"name": "Suunto Vertical",
"productType": "SPORT_WATCH"
}
}

Refer to Workout API Documentation for details about the workout -object.


Legacy Form-Based Notifications

The legacy format for workout notifications is still supported. These notifications are sent to a separate notification URL and use form-encoded parameters in the request body.


Content-Type: application/x-www-form-urlencoded

workoutid=workout123&username=johndoe123


New Route Notification

Content-Type: application/json

{ 
"type": "ROUTE_CREATED",
"username": "johndoe123",
"route": {
"id": "route123",
"description": "A scenic trail through the mountains.",
"visibility": "public",
"activityIds": [101, 102],
"startPoint": { "altitude": 150.5, "latitude": 60.192059, "longitude": 24.945831 },
"centerPoint": { "altitude": 200.0, "latitude": 60.200000, "longitude": 24.950000 },
"endPoint": { "altitude": 100.0, "latitude": 60.203038, "longitude": 24.960817 },
"created": 1702215600000,
"averageSpeed": 5.8,
"totalDistance": 12345.67,
"modified": 1702302000000,
"watchEnabled": true,
"turnWaypointsEnabled": true
}
}

Refer to the Route API Documentation for details about the route -object.

24/7 Activity Data Notification

Content-Type: application/json

{ 
"type": "SUUNTO_247_ACTIVITY_CREATED",
"username": "johndoe123",
"samples": [
{
"timestamp": "2024-12-10T14:50:00.000+02:00",
"entryData": { "HR": 71, "StepCount": 169, "EnergyConsumption": 29308 }
},
{
"timestamp": "2024-12-10T15:00:00.000+02:00",
"entryData": { "HR": 75, "StepCount": 200, "EnergyConsumption": 31000 }
}
]
}

Refer to the 247 activity API Documentation for details about the samples -array.

24/7 Sleep Data Notification

Content-Type: application/json

{
"type": "SUUNTO_247_SLEEP_CREATED",
"username": "johndoe123",
"samples": [
{
"timestamp": "2025-01-05T23:29:00.000+02:00",
"entryData": {
"DeepSleepDuration": 2850,
"LightSleepDuration": 22470,
"REMSleepDuration": 5280,
"Duration": 32460,
"HRAvg": 45,
"HRMin": 42,
"SleepQualityScore": 81,
"SleepId": 1736112540,
"BedtimeStart": "2025-01-05T23:29:00.000+02:00",
"BedtimeEnd": "2025-01-06T08:30:00.000+02:00",
"MaxSpo2": 1,
"Altitude": 12,
"AvgHRV": 47,
"AvgHRVSampleCount": 107,
"IsNap": false,
"SleepOnsetLatencyDuration": 1200,
"WakeAfterSleepOnsetDuration": 630,
"WakeBeforeOffBedDuration": 30,
"DateTime": "2025-01-05T23:29:00.000+02:00"
}
}
]
}

Refer to the 247 sleep API Documentation for details about the samples -array.

24/7 Recovery Data Notification

Content-Type: application/json

{
"type": "SUUNTO_247_RECOVERY_CREATED",
"username": "johndoe123",
"samples": [
{
"timestamp": "2025-01-05T23:29:00.000+02:00",
"entryData": {
"Balance": 0.93,
"StressState": 3
}
}
]
}

Refer to the 247 recovery API Documentation for details about the samples -array.

Handling Webhook Requests

To handle webhook requests, follow these steps:

1. Verify the Signature

Webhook requests include an X-HMAC-SHA256-Signature -header. This signature is calculated from the request body and your notification secret. To ensure the authenticity of the request:

  • Compute the HMAC SHA-256 signature of the request body using your notification secret. See example code below.

  • Compare your computed signature with the value in the X-HMAC-SHA256-Signature header. If they match, the request is authentic.

Example Request Headers:

POST /webhook-endpoint HTTP/1.1
Host: your-webhook-url.com
Content-Type: application/json
User-Agent: SuuntoPartnerIntegration/1.0.0
X-HMAC-SHA256-Signature: 0eb0ee2b4abeb58c34eafad037258cdd8e315eccb1b6509a5e5730f8aa1fa53f

2. Identify the User

  • Each request includes a username field containing the Suunto user ID. Use this ID to locate the corresponding user in your system.

  • The Suunto user ID can be obtained during the OAuth authorization flow when exchanging the authorization code for an access token. The user ID is available as:

    • The user field in the OAuth access token response.

    • A custom claim user in the JWT access token.

Refer to How to Start -page for instructions on exchanging the authorization code for an access token.

3. Respond to Webhooks

  • Respond with an HTTP 2XX status code (e.g., 200 OK).

  • Any response content is acceptable.

  • Responses must be sent within 2 seconds.

Retry Behavior

If your system does not respond with a successful HTTP status (e.g., 200 OK) or times out, the webhook request will be retried using an exponential backoff delay. However, excessive failures within a defined time frame will trigger a circuit breaker mechanism, which pauses all notification sending temporarily for your app.

Example Code for Signature Calculation

The following example shows how to compute the HMAC SHA-256 signature in Kotlin:

package com.amersports.integration.partners.utils

import org.apache.commons.codec.binary.Hex
import java.io.InputStream
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

object NotificationSignature {
private const val BUFFER_SIZE = 8192 // 8 KB buffer

fun create(secret: String, inputStream: InputStream): String {
val signingKey = SecretKeySpec(secret.toByteArray(), "HmacSHA256")

val mac = Mac.getInstance("HmacSHA256")
mac.init(signingKey)

val buffer = ByteArray(BUFFER_SIZE)

while (true) {
val bytesRead = inputStream.read(buffer)
if (bytesRead == -1) break
mac.update(buffer, 0, bytesRead)
}

val signatureBytes = mac.doFinal()
return Hex.encodeHexString(signatureBytes)
}
}