---
title: "Strava Webhooks Guide | Apps for Strava"
canonical: https://appsforstrava.com/developers/webhooks
source: Markdown mirror of https://appsforstrava.com/developers/webhooks — the HTML page is canonical.
---

# Webhooks

Receive real-time notifications when activities are created, updated, or deleted. No more polling the API for changes.

## How Webhooks Work

Instead of repeatedly polling the API to check for new activities, Strava sends HTTP POST requests to your server whenever relevant events occur.

Strava

POST /webhook

Your Server

## Supported Events

Webhooks are triggered for the following events:

| Object Type | Event | Description |
| --- | --- | --- |
| activity | create | New activity created or made public |
| activity | update | Title, type, or privacy changed |
| activity | delete | Activity deleted or made private |
| athlete | update | Athlete deauthorized your app |

**Privacy Note:** If your app only has `activity:read` scope (not `activity:read_all`), you'll receive a `delete` event when an activity is changed to "Only You" visibility, and a `create` event when it becomes public again.

## Webhook Payload

Each webhook POST contains a JSON payload with the following structure:

```
{
  "object_type": "activity",
  "object_id": 12345678987654321,
  "aspect_type": "create",
  "updates": {},
  "owner_id": 12345678,
  "subscription_id": 123456,
  "event_time": 1704931200
}
```

| Field | Type | Description |
| --- | --- | --- |
| object\_type | string | "activity" or "athlete" |
| object\_id | long | Activity ID or Athlete ID |
| aspect\_type | string | "create", "update", or "delete" |
| updates | object | Changed fields (for update events) |
| owner\_id | long | Athlete ID who owns the object |
| subscription\_id | integer | Your webhook subscription ID |
| event\_time | long | Unix timestamp of the event |

## Creating a Webhook Subscription

Setting up webhooks is a two-step process: creating the subscription and verifying your callback URL.

### Step 1: Create Subscription Request

```
curl -X POST https://www.strava.com/api/v3/push_subscriptions \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d callback_url=https://your-app.com/webhook \
  -d verify_token=YOUR_RANDOM_STRING
```

### Step 2: Verify Your Callback URL

Strava will immediately send a GET request to your callback URL with these parameters:

```
GET /webhook?hub.mode=subscribe&hub.challenge=abc123&hub.verify_token=YOUR_RANDOM_STRING
```

Your server must respond within **2 seconds** with status 200 and echo the challenge:

```
{
  "hub.challenge": "abc123"
}
```

### Express.js Example

```
const express = require('express');
const app = express();
app.use(express.json());

const VERIFY_TOKEN = process.env.STRAVA_VERIFY_TOKEN;

// Webhook verification (GET)
app.get('/webhook', (req, res) => {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === VERIFY_TOKEN) {
    console.log('Webhook verified');
    res.json({ 'hub.challenge': challenge });
  } else {
    res.status(403).send('Forbidden');
  }
});

// Webhook events (POST)
app.post('/webhook', (req, res) => {
  const event = req.body;
  console.log('Received webhook:', event);

  // Process the event asynchronously
  processWebhookEvent(event);

  // Always respond quickly with 200
  res.status(200).send('OK');
});

async function processWebhookEvent(event) {
  if (event.object_type === 'activity') {
    if (event.aspect_type === 'create') {
      // Fetch the new activity
      const activity = await fetchActivity(event.object_id, event.owner_id);
      // Do something with it...
    } else if (event.aspect_type === 'delete') {
      // Handle deletion
      await deleteActivityFromDatabase(event.object_id);
    }
  } else if (event.object_type === 'athlete') {
    // Athlete deauthorized - clean up their data
    await removeAthleteData(event.owner_id);
  }
}
```

## Managing Subscriptions

### View Your Subscription

```
curl -X GET "https://www.strava.com/api/v3/push_subscriptions?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"
```

### Delete Your Subscription

```
curl -X DELETE "https://www.strava.com/api/v3/push_subscriptions/SUBSCRIPTION_ID?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"
```

**Limit:** Each application can have only one webhook subscription. This single subscription receives events for all athletes who have authorized your app.

## Webhook Best Practices

Respond Quickly

Return 200 OK within 2 seconds. Process events asynchronously.

Handle Retries

Strava retries failed deliveries, up to three total delivery attempts. Make your handler idempotent.

Verify the Source

Validate the subscription\_id matches yours to prevent spoofed events.

Fetch Full Data

Webhooks only contain IDs. Use the API to fetch complete activity/athlete data.

Handle Deauthorization

When you receive an athlete update, clean up their data from your system.

## Next Steps

- [Code Examples](https://appsforstrava.com/developers/examples): Complete webhook implementations in multiple languages
- [Best Practices](https://appsforstrava.com/developers/best-practices): Production deployment tips and error handling
