Agent skill

Strava Webhooks Skill

Receive and verify Strava webhooks (Webhook Events API). Use when setting up Strava push subscriptions, implementing the GET subscription validation handshake, debugging the hub.challenge / hub.verify_token exchange, or handling activity and athlete events like activity create, activity update, activity delete, and athlete deauthorization.

Install this skill

npx skills add hookdeck/webhook-skills --skill strava-webhooks


When to Use This Skill

  • How do I receive Strava webhooks?
  • How do I set up a Strava push subscription?
  • How do I implement the Strava subscription validation (GET hub.challenge) handshake?
  • Why is my Strava subscription creation failing / callback validation failing?
  • How do I handle Strava activity and athlete events?
  • How do I detect a Strava athlete deauthorization?

How Strava Webhooks Differ

Strava push events are NOT cryptographically signed — there is no per-event signature, HMAC, or shared-secret header to verify on each POST. Authenticity is established once, at subscription time, via a GET handshake:

  1. You POST to https://www.strava.com/api/v3/push_subscriptions with client_id, client_secret, callback_url, and a self-chosen verify_token.
  2. Strava immediately GETs your callback_url with hub.mode=subscribe, hub.challenge=<random>, and hub.verify_token=<your token>.
  3. You confirm hub.verify_token matches your token and respond within 2 seconds with HTTP 200 and JSON body {"hub.challenge":"<echoed value>"}.

After that, Strava POSTs thin event payloads (an object_id, not full data) to the same callback. Acknowledge every event with 200 within 2 seconds or Strava retries (up to 3 total attempts). Fetch full activity/athlete data from the Strava REST API using the object_id. Only ONE subscription is allowed per API application.

Verification (core)

There is no signature to check on events — the security boundary is the GET validation handshake. Compare hub.verify_token against your stored token with a timing-safe comparison, then echo hub.challenge:

const crypto = require('crypto');

// GET /webhooks/strava — subscription validation handshake
function handleValidation(query, expectedToken) {
  const mode = query['hub.mode'];
  const token = query['hub.verify_token'] || '';
  const challenge = query['hub.challenge'];

  const a = Buffer.from(token);
  const b = Buffer.from(expectedToken);
  const tokenOk = a.length === b.length && crypto.timingSafeEqual(a, b);

  if (mode === 'subscribe' && tokenOk) {
    return { status: 200, body: { 'hub.challenge': challenge } }; // exact key name
  }
  return { status: 403, body: 'Forbidden' };
}

For complete handlers (GET validation + POST event dispatch) with tests, see:

Common Event Types

Events are identified by object_type + aspect_type (there is no single event name string). All values below are exact.

object_typeaspect_typeTriggered When
activitycreateAn athlete uploads/creates a new activity
activityupdateAn activity's title, type, or privacy changes
activitydeleteAn activity is deleted
athleteupdateAthlete deauthorizes your app (updates = {"authorized":"false"})

updates for an activity update may contain title, type, and private ("true" / "false"). A single save can produce multiple events.

For the full reference, see Strava Webhook Events API.

Event Payload Structure

{
  "object_type": "activity",
  "object_id": 1360128428,
  "aspect_type": "create",
  "owner_id": 134815,
  "subscription_id": 120475,
  "event_time": 1516126040,
  "updates": {}
}

Environment Variables

STRAVA_CLIENT_ID=12345                 # Strava API application ID
STRAVA_CLIENT_SECRET=xxxxxxxx          # Strava API application secret
STRAVA_VERIFY_TOKEN=your_random_token  # Self-chosen token echoed during validation
STRAVA_SUBSCRIPTION_ID=120475          # Optional: reject events from other subscriptions

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 strava --path /webhooks/strava

Then create the subscription so Strava validates your callback:

curl -X POST https://www.strava.com/api/v3/push_subscriptions \
  -F client_id=$STRAVA_CLIENT_ID \
  -F client_secret=$STRAVA_CLIENT_SECRET \
  -F callback_url=https://<your-tunnel-url>/webhooks/strava \
  -F verify_token=$STRAVA_VERIFY_TOKEN

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 3, 2026

View on GitHub →