Stridee
Stridee Docs

Events

What the platform emits, what an event body contains, and why it contains so little.

An event says that something happened: a type, the user it concerns, and the ids and summary you need to act on it. It never carries the recording itself — that is one signed call away.

Event types

TypeEmitted when
pingYou sent a test delivery — see Sending a pingLive
activity.createdA finished workout landed from a providerLive
wellness.createdA sleep, daily, HRV, stress or fitness summary landed — see WellnessLive
wellness.updatedThe provider revised a summary you were already told aboutLive
account.connectedOne of your users linked a providerLive
account.disconnectedThat access ended — by your call, or by the user through their management linkLive
account.reauth_requiredThe provider stopped accepting our credentials — your user has to reconnectLive
account.deletedYou dropped your reference to one of your usersLive
activity.updatedTitle, gear or laps changed after uploadPlanned
activity.deletedThe user removed it on the provider sidePlanned
workout.pushedA structured session reached the watchPlanned
workout.completedA pushed session came back as donePlanned

Planned types are designed but not emitted yet. Every endpoint receives every type, so switch on type and ignore what you don't recognise.

What an event looks like

activity.created
{
  "id": "9f2c1e7a-4b83-4d21-9a6e-3c5f0d8b71a4",
  "type": "activity.created",
  "created": "2026-08-04T06:14:02Z",
  "webhook_id": "2d7b45c1-8e0a-4f36-b512-9c7d3a6e04f8",
  "nonce": "Kd4nWpLbEa9xTvRm2Cj7Lz0Bq2vNhCz7",
  "user_id": "4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6",
  "provider": "coros",
  "data": {
    "object": "activity",
    "id": "0f31a8c4-59d2-4e07-b6a1-8c74e2f95d30",
    "provider_activity_id": "465061532017233920",
    "sport": "run",
    "start_time": "2026-08-04T06:12:00Z",
    "device": "PACE 3",
    "name": "Morning Run",
    "file": {
      "format": "fit",
      "url": "https://api.stridee.com/v1/activities/0f31a8c4-59d2-4e07-b6a1-8c74e2f95d30/file"
    }
  }
}

This is what a delivery opens to, never what is on the wire.

Field
idThe event. The same event sent to three endpoints has one id.
user_idYour user, by the id /v1/connect returned. Absent on ping.
providerWhich integration caused it. Absent on ping.
webhook_idWhich of your endpoints this copy went to.
nonceFresh per delivery — echo it back.
dataThe object's type, ids and summary.

Getting the file

data.file.url on an activity.created is the recording itself — the FIT, TCX or GPX the device wrote, unparsed. It is an ordinary signed request, so sign it the way you sign every other call:

Shell
GET /v1/activities/0f31a8c4-59d2-4e07-b6a1-8c74e2f95d30/file

It answers 302 with a short-lived URL that carries its own authorization. Follow the redirect; don't store what it points at, because it expires in minutes. The URL in the event body does not expire — keep that one and call it again whenever you need the bytes.

data.id is our id for this activity, and it is yours alone. Another developer whose user is the same athlete gets a different id for the same workout.

file is null when there is nothing to fetch — Wahoo, for instance, won't share a recording that started in a third-party app. The workout still happened; don't retry it.

Access is granted when an activity arrives, so it covers exactly the time your user was connected to you. Disconnecting stops new activities but does not take back ones already shared — their links keep working and they stay listed below.

Listing what you were sent

Deliveries can be missed, and nothing retries them. GET /v1/activities is how you catch up: every activity you have been granted, newest first, in the same shape the event carried.

Shell
GET /v1/activities?since=2026-08-12T00:00:00Z&until=2026-08-13T00:00:00Z&limit=50
JSON
{
  "activities": [
    {
      "id": "7e14a9c3-2b60-4f85-9d3a-6c081ef47b52",
      "user_id": "4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6",
      "external_user_id": "01JQ8F3M2KX7A9VBRC4T6NDZWE",
      "provider": "garmin",
      "provider_activity_id": "19284736501",
      "sport": "running",
      "name": "Threshold intervals",
      "device": "Forerunner 965",
      "start_time": "2026-08-13T06:14:00Z",
      "received_at": "2026-08-13T07:02:11Z",
      "file": {
        "format": "fit",
        "size": 412880,
        "url": "https://api.stridee.com/v1/activities/7e14a9c3-2b60-4f85-9d3a-6c081ef47b52/file"
      }
    }
  ],
  "has_more": true,
  "next_starting_after": "7e14a9c3-2b60-4f85-9d3a-6c081ef47b52",
  "total": 1284
}

A row is an activity.created flattened: user_id and provider are the envelope's, everything from provider_activity_id down is the data block, and file.url is the same download URL. Hand one to the code that already handles a delivery.

Page against received_at, not start_time. received_at is when we granted you the activity — within a second of when your endpoint was called — and it is the only axis that moves forward. Providers backfill, so a ride from March can arrive today, and a job paging by start time would step straight over it.

Parameter
user_idOne athlete, by our id for them. GET /v1/accounts?external_user_id=… is the way back from yours.
providerAny provider on Provider support. An unknown one is an empty page, not an error.
sinceGranted at or after this RFC 3339 instant.
untilGranted strictly before it — half-open, so one window's until is the next one's since and nothing is counted twice.
starting_afterThe id of the last activity on the previous page. See Paging.
limitDefaults to 50, capped at 200.

A nightly reconcile is since=<last run>&until=<this run>, walked with starting_after while has_more is true — then dedupe on id against what your handler already stored. Pass both ends of the window and the set you are walking cannot change underneath you.

Ordering and duplicates

  • Order is not guaranteed. Providers backfill and correct, so a workout from March can arrive today.
  • The same delivery can arrive twice — a resend reuses its webhook-id. Dedupe on it, and still echo the nonce.
  • An event id is not a delivery id. One event sent to three endpoints is one id and three webhook-ids. Both are UUID v4 with no prefix; store them as strings and never parse one.

Treat an event as a signal to reconcile, not a transaction to apply, and none of these matter.

Something wrong or missing on this page? Tell us in Discord. Need something the API doesn’t do yet? Request it on the roadmap.