---
title: "Why Is My Strava Webhook Not Firing?"
canonical: "https://aifitnessapi.com/fix/strava-webhook-not-firing"
cluster: "Troubleshooting"
primary_query: "strava webhook not firing"
last_reviewed: "2026-07-09"
description: "Strava webhook not firing? Usually the subscription was never created because the validation handshake failed. How to verify it exists and fix it."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"Why Is My Strava Webhook Not Firing?\", AIFitnessAPI, https://aifitnessapi.com/fix/strava-webhook-not-firing"
---

# Why Is My Strava Webhook Not Firing?

> The most common reason a Strava webhook never fires is that the subscription was never created: creating one is a two-step handshake, and if your callback fails to echo the hub.challenge as JSON with HTTP 200 within about two seconds, Strava silently abandons it. First confirm a subscription actually exists by calling GET push_subscriptions with your client_id and client_secret; an empty array means nothing will ever fire. Then make sure your callback is a public HTTPS URL that answers the validation GET correctly, and remember only one subscription is allowed per application.

- Canonical: https://aifitnessapi.com/fix/strava-webhook-not-firing
- Last reviewed: 2026-07-09
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "Why Is My Strava Webhook Not Firing?", AIFitnessAPI, https://aifitnessapi.com/fix/strava-webhook-not-firing

---

Your Strava webhook isn't firing, and the most likely reason is the one that's easiest to miss: the subscription was never created in the first place. Creating a subscription is a two-step handshake, and if the validation step failed, Strava silently gave up and no events will ever arrive. The fastest fix is to confirm a subscription actually exists, and if it doesn't, get your callback endpoint answering Strava's validation `GET` correctly.

Below are the causes ranked from most to least common, each with the concrete check and fix. If your problem is really that data shows up late rather than never, see [Wearable data delayed or missing](/fix/wearable-data-delayed). For the full happy-path setup, see the [Strava API integration guide](/integrate/strava-api).

## Most likely causes, ranked

1. **The subscription was never created** because the validation `GET` handshake failed — your callback didn't echo `hub.challenge` as JSON with HTTP 200 within ~2 seconds.
2. **Your callback isn't a public HTTPS URL.** `localhost`, private IPs, and self-signed certs won't validate.
3. **A stale subscription owns the only slot.** Strava allows exactly one subscription per application, so a second create silently fails.
4. **Events *are* firing** — but they're lightweight pointers, and your handler crashes trying to read activity fields that aren't in the payload.
5. **Private activities are invisible** because the athlete granted `activity:read` instead of `activity:read_all`.

## Step 1: Confirm a subscription actually exists

Before debugging anything else, ask Strava whether your app even has a subscription. View the current one for your application:

```bash
curl -G https://www.strava.com/api/v3/push_subscriptions \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET
```

An empty array (`[]`) means no subscription exists, so nothing will ever fire — go to Step 2. If a subscription is returned, note its `id` and `callback_url`; if that URL is stale or wrong, jump to Step 4 to delete and recreate it.

## Step 2: Answer the validation handshake correctly

Creating a subscription is a **two-step handshake**. You `POST` to request it, and Strava then immediately issues a `GET` to your `callback_url` to validate it. If that `GET` isn't answered correctly, the subscription is never created.

Step 2a — request the subscription:

```bash
curl -X POST https://www.strava.com/api/v3/push_subscriptions \
  -F client_id=YOUR_CLIENT_ID \
  -F client_secret=YOUR_CLIENT_SECRET \
  -F callback_url=https://example.com/webhook \
  -F verify_token=STRAVA
```

Step 2b — Strava calls your callback with a validation `GET`, for example:

```http
GET https://example.com/webhook?hub.verify_token=STRAVA&hub.challenge=15f7d1a91c1f40f8a748fd134752feb3&hub.mode=subscribe
```

Your endpoint must, within **two seconds**, return HTTP **200** and echo `hub.challenge` back as `application/json`:

```json
{ "hub.challenge": "15f7d1a91c1f40f8a748fd134752feb3" }
```

Per Strava's docs, the most common reason a subscription fails to be created is a failure to respond to this validation `GET` in a timely manner, or failing to echo the `hub.challenge` field correctly. A minimal handler looks like this:

```ts
// GET /webhook — Strava's subscription validation
app.get("/webhook", (req, res) => {
  const mode = req.query["hub.mode"];
  const token = req.query["hub.verify_token"];
  const challenge = req.query["hub.challenge"];

  // Optional but recommended: verify the token you sent on create
  if (mode === "subscribe" && token === "STRAVA") {
    // Echo the challenge back as JSON, status 200, fast — no heavy work here
    return res.status(200).json({ "hub.challenge": challenge });
  }
  return res.sendStatus(403);
});
```

Handshake checklist:

- Endpoint returns **200** — not a `301`/`302` redirect, and not `401` behind auth middleware.
- Body is exactly `{"hub.challenge": "<value>"}` with `Content-Type: application/json`.
- The `hub.verify_token` matches the `verify_token` you sent on create.
- The handler is **fast** (under 2 seconds) — do no database or network work in the validation path.

## Step 3: Make sure the callback is public HTTPS

The callback URL must be **publicly reachable over HTTPS**. `localhost`, private/internal IPs, and untrusted or self-signed certificates will not validate, so the handshake in Step 2 fails before your code ever runs. During development, expose a public HTTPS URL with a tunnel such as ngrok — this is exactly what Strava's own webhook example uses. Also confirm no WAF or auth middleware is silently blocking Strava's request.

## Step 4: Free the single subscription slot

Each application may have **only one subscription**, and that single subscription receives events for *all* athletes who authorized your app. A very common "not firing" story is: you spun up a new callback URL, tried to create a fresh subscription, and it failed because the old one still owns the slot. Delete the stale subscription (using its `id` from Step 1) to free the slot, then recreate with Step 2:

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

## Step 5: Handle the event as a pointer, not the activity

If the subscription exists and validates but you still see "nothing," the events may be arriving and your handler may be quietly crashing. Webhook payloads are small **notifications**, not the activity data. A `POST` to your callback looks like this:

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

Return **200 to Strava immediately**, then process asynchronously. To get the actual activity, call the REST API with that athlete's access token, for example `GET /activities/{object_id}` using `object_id` from the event. A handler that tries to read activity fields (distance, name, etc.) straight off the pointer, or whose follow-up fetch fails on an expired token, looks exactly like "the webhook is broken." Add logging as close to the network edge as possible — an unhandled exception in payload processing is a frequent cause of "missing updates."

## Step 6: Grant `activity:read_all` for private activities

If a webhook fires but the follow-up fetch returns nothing (or 404s) specifically for private or hidden activities, the athlete most likely authorized only `activity:read`. **Private activities require the `activity:read_all` scope**, granted at OAuth time. Request it in your authorization URL and have affected athletes re-consent. (Verify current scope naming on the [Strava authentication docs](https://developers.strava.com/docs/authentication/).)

## Still stuck? Diagnostic checklist

Run these in order:

- **Does a subscription exist?** `GET /push_subscriptions` (Step 1). Empty array = nothing will ever fire.
- **Is the callback public HTTPS?** No `localhost`, no self-signed cert, no auth/WAF in front of it.
- **Test reachability yourself.** Hit your callback with a `GET` carrying a fake `hub.challenge` — it should echo it. Then `POST` a sample event body — it should return 200. Strava's own advice is to `POST` to your callback manually and confirm a 200.
- **Only one slot.** If create fails, delete the stale subscription first (Step 4).
- **Log at the edge.** Confirm events aren't arriving and silently erroring in your handler.
- **Check the scope.** Missing private activities usually means `activity:read` instead of `activity:read_all`.

If events are arriving but appear late rather than never, that's a different problem — see [Wearable data delayed or missing](/fix/wearable-data-delayed). For end-to-end setup including OAuth and scopes, see the [Strava API integration guide](/integrate/strava-api).

## FAQ

### How do I check whether my Strava webhook subscription actually exists?

Send a GET request to https://www.strava.com/api/v3/push_subscriptions with your client_id and client_secret. If it returns an empty array, no subscription exists and no events will ever fire, so you need to create one and pass the validation handshake.

[Permalink](https://aifitnessapi.com/fix/strava-webhook-not-firing#faq-1)

### Why does creating my Strava subscription fail even though my server is running?

When you POST to create a subscription, Strava immediately issues a validation GET to your callback_url. If your endpoint does not return HTTP 200 and echo hub.challenge back as application/json within about two seconds, the subscription is never created. Localhost, self-signed certs, redirects, and auth middleware in front of the callback all cause this to fail.

[Permalink](https://aifitnessapi.com/fix/strava-webhook-not-firing#faq-2)

### Can I have more than one Strava webhook subscription per app?

No. Each application may have only one subscription, and it receives events for all athletes who authorized the app. If you try to create a second one while a stale subscription still owns the slot, the create fails. Delete the old subscription first with DELETE push_subscriptions.

[Permalink](https://aifitnessapi.com/fix/strava-webhook-not-firing#faq-3)

### The webhook fires but my code errors. Is the webhook broken?

Usually not. Strava events are lightweight pointers containing fields like object_id and owner_id, not the full activity. Return 200 immediately, then fetch the activity from the REST API using object_id and the athlete's token. A handler that crashes reading missing fields, or whose follow-up fetch fails on an expired token, looks like a webhook that isn't firing.

[Permalink](https://aifitnessapi.com/fix/strava-webhook-not-firing#faq-4)

### Why can't I see private activities from the webhook?

Private and hidden activities require the activity:read_all scope, granted at OAuth time. If the athlete authorized only activity:read, your follow-up fetch returns nothing for those activities. Request activity:read_all and have the athlete re-consent. Verify current scope naming on the Strava authentication docs.

[Permalink](https://aifitnessapi.com/fix/strava-webhook-not-firing#faq-5)
