Skip to content

Webhooks

Webhooks notify your application when an activity, a test or an uploaded file changes in SweatStack. SweatStack sends an HTTP POST to the endpoints you register.

Set up webhooks

Register one or more endpoint URLs on your app's settings page at Settings → API. SweatStack sends every event to every registered endpoint.

When you register an endpoint, SweatStack generates a webhook secret. Use it to verify that an event came from SweatStack. You can regenerate (roll) the secret at any time. The new secret takes effect immediately.

Event types

SweatStack sends these events:

Event type Sent when
activity_created An activity was created.
activity_updated An activity was updated.
activity_deleted An activity was deleted.
test_created A test was created.
test_updated A test was updated.
test_deleted A test was deleted.
source_processed An uploaded file was processed into activities.
source_failed An uploaded file failed to process.

Webhook payload

Every event has the same shape:

{
  "user_id": "string",
  "event_type": "activity_created",
  "resource_id": "string",
  "timestamp": "2026-05-06T12:00:00Z"
}

resource_id is the ID of the resource the event is about: an activity ID for activity_* events, a test ID for test_* events, a source ID for source_* events.

The payload carries no resource data. When you receive an event, fetch the resource from the matching endpoint.

Delivery latency

There is no latency guarantee. In the typical case, SweatStack sends the event within seconds of the activity reaching it: Garmin and Intervals.icu usually deliver a new activity within seconds, and SweatStack's own processing adds seconds, not minutes, before the event goes out.

The source platform is the exception. Garmin's own sync to SweatStack is sometimes much slower, and then the webhook is delayed by the same amount. Build for the typical case of seconds, and handle delayed and out-of-order arrivals as normal, not as errors.

Response time requirement

Your endpoint must return a 2xx status code within 2 seconds to acknowledge an event.

If a delivery times out, cannot connect, or gets a 5xx, SweatStack retries it on a fixed schedule: 8 s, 32 s, 128 s, 512 s, 34 min, 2.3 h, 9.1 h. That is eight attempts over about twelve hours. After the last attempt, SweatStack drops the event for that endpoint. Each endpoint is retried on its own: an endpoint that acknowledged an event never receives it again because another endpoint failed.

A 4xx answer is final, and SweatStack does not retry it, with two exceptions: 408 Request Timeout and 429 Too Many Requests are retried like a timeout. If your endpoint answers 404 or 401, check the URL and your signature verification in your API settings. SweatStack does not redeliver those events.

Verify webhook signatures

SweatStack signs every event with HMAC-SHA256. The signature is in the X-Sweatstack-Signature header, in the format t={timestamp},v1={signature}.

To verify an event:

  1. Extract the timestamp and the signature from the header.
  2. Build the signed payload: {timestamp}.{json_body}.
  3. Compute the HMAC-SHA256 of the signed payload with your webhook secret.
  4. Compare your result with the signature, using a constant-time comparison.
  5. Reject the event if the timestamp is older than your tolerance. 5 minutes is a reasonable tolerance against replays.

The FastAPI helper ships a verify_signature function that does this.

Best practices

  • Verify signatures. Always verify before you process the event.
  • Return fast. Acknowledge with a 2xx within 2 seconds, then process asynchronously.
  • Be idempotent. SweatStack can deliver an event more than once. Make your handling idempotent on resource_id and event_type.
  • Log events. Keep a record of received webhooks for debugging and reconciliation.

Patterns and gotchas

Webhooks are app-level, not per-user. An endpoint registered in your API settings receives events for every user of your app. The user_id field on the payload tells you which one.

Payloads carry IDs, not data. An event tells you that something happened. It does not include the activity, test or source itself. To enrich an event with the resource, your handler calls the relevant SweatStack endpoint with that user's access token. So your webhook handler needs a way to look up tokens by user_id, typically a small key-value store or your own user database.

Mobile apps cannot receive webhooks directly. A webhook is an HTTP POST to a public endpoint, and a phone is not one. The standard pattern is a server-side gateway (a small worker or function) that receives the SweatStack webhook, looks up the user's push token, and sends the event on to APNs (iOS) or FCM (Android). The gateway is also the right place to do the enrichment described above. See the native mobile app guide for an end-to-end example.

Plan for replays. SweatStack retries on failure (see Response time requirement). If your endpoint processed an event and then timed out, you receive the event twice. Idempotency on (resource_id, event_type) keeps duplicates from corrupting state.

A deleted resource is gone. On activity_deleted and the other *_deleted events, the resource no longer exists, so fetching it returns 404. Acknowledge the event and remove your copy. Do not fail your handler on that 404, or SweatStack keeps retrying an event you have already handled. A user who deletes all of their activities produces one event per activity, in a burst.

Next steps