Skip to content

Commit 3be914e

Browse files
authored
Merge pull request #102 from lajennylove/develop
feat: Add comprehensive webhook subscription support
2 parents 99386cc + d8ca2c4 commit 3be914e

File tree

11 files changed

+1459
-159
lines changed

11 files changed

+1459
-159
lines changed

README.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,152 @@ $client->getStreamsActivity($id, $types, $resolution = null, $series_type = 'dis
211211
$client->getStreamsEffort($id, $types, $resolution = null, $series_type = 'distance');
212212
$client->getStreamsSegment($id, $types, $resolution = null, $series_type = 'distance');
213213
$client->getStreamsRoute($id);
214+
$client->createWebhookSubscription($clientId, $clientSecret, $callbackUrl, $verifyToken);
215+
$client->listWebhookSubscriptions($clientId, $clientSecret);
216+
$client->deleteWebhookSubscription($clientId, $clientSecret, $subscriptionId);
214217
```
215218

219+
## Webhook Integration
220+
221+
StravaPHP now includes comprehensive webhook support for real-time event notifications. This allows you to receive instant notifications when activities are created, updated, or deleted.
222+
223+
### Webhook Subscription Management
224+
225+
```php
226+
<?php
227+
include 'vendor/autoload.php';
228+
229+
use Strava\API\Client;
230+
use Strava\API\Service\REST;
231+
use GuzzleHttp\Client as GuzzleClient;
232+
233+
// Create API client
234+
$adapter = new GuzzleClient(['base_uri' => 'https://www.strava.com/api/v3/']);
235+
$service = new REST('YOUR_ACCESS_TOKEN', $adapter);
236+
$client = new Client($service);
237+
238+
// Your Strava app credentials
239+
$clientId = 12345;
240+
$clientSecret = 'your_client_secret';
241+
$callbackUrl = 'https://yourdomain.com/webhook-endpoint.php';
242+
$verifyToken = 'your_random_verify_token';
243+
244+
try {
245+
// Create a webhook subscription
246+
$subscription = $client->createWebhookSubscription(
247+
$clientId,
248+
$clientSecret,
249+
$callbackUrl,
250+
$verifyToken
251+
);
252+
253+
echo "Webhook subscription created: " . $subscription['id'] . "\n";
254+
255+
// List existing subscriptions
256+
$subscriptions = $client->listWebhookSubscriptions($clientId, $clientSecret);
257+
echo "Active subscriptions: " . count($subscriptions) . "\n";
258+
259+
// Delete a subscription
260+
$client->deleteWebhookSubscription($clientId, $clientSecret, $subscription['id']);
261+
echo "Subscription deleted\n";
262+
263+
} catch (Exception $e) {
264+
echo "Error: " . $e->getMessage() . "\n";
265+
}
266+
```
267+
268+
### Webhook Event Handling
269+
270+
```php
271+
<?php
272+
// webhook-endpoint.php
273+
include 'vendor/autoload.php';
274+
275+
use Strava\API\Webhook;
276+
277+
// Handle subscription challenge (when creating webhook)
278+
$verifyToken = 'your_random_verify_token';
279+
$challengeResult = Webhook::handleSubscriptionChallenge($verifyToken);
280+
281+
if ($challengeResult['success']) {
282+
// Send challenge response to Strava
283+
Webhook::sendChallengeResponse($challengeResult['challenge']);
284+
}
285+
286+
// Process incoming webhook events
287+
$eventResult = Webhook::processEvent();
288+
289+
if ($eventResult['success']) {
290+
$event = $eventResult['event'];
291+
292+
// Handle different event types
293+
switch (Webhook::getEventType($event)) {
294+
case 'activity.create':
295+
echo "New activity: " . $event['object_id'] . "\n";
296+
// Process new activity
297+
break;
298+
299+
case 'activity.update':
300+
echo "Updated activity: " . $event['object_id'] . "\n";
301+
// Process activity update
302+
break;
303+
304+
case 'activity.delete':
305+
echo "Deleted activity: " . $event['object_id'] . "\n";
306+
// Process activity deletion
307+
break;
308+
309+
case 'athlete.update':
310+
echo "Updated athlete: " . $event['object_id'] . "\n";
311+
// Process athlete update
312+
break;
313+
}
314+
315+
// Send success response
316+
http_response_code(200);
317+
echo json_encode(['status' => 'success']);
318+
} else {
319+
// Handle error
320+
http_response_code(400);
321+
echo json_encode(['error' => $eventResult['error']]);
322+
}
323+
```
324+
325+
### Webhook Helper Methods
326+
327+
The `Webhook` class provides several utility methods:
328+
329+
```php
330+
// Get event type (e.g., 'activity.create')
331+
$eventType = Webhook::getEventType($event);
332+
333+
// Check if event is for specific object type
334+
$isActivity = Webhook::isObjectType($event, 'activity');
335+
$isAthlete = Webhook::isObjectType($event, 'athlete');
336+
337+
// Check if event is specific aspect type
338+
$isCreate = Webhook::isAspectType($event, 'create');
339+
$isUpdate = Webhook::isAspectType($event, 'update');
340+
$isDelete = Webhook::isAspectType($event, 'delete');
341+
342+
// Verify webhook signature (if using signature verification)
343+
$isValid = Webhook::verifySignature($payload, $signature, $secret);
344+
```
345+
346+
### Webhook Events
347+
348+
Strava webhooks support the following event types:
349+
350+
- **Activity Events:**
351+
- `activity.create` - New activity created
352+
- `activity.update` - Activity updated
353+
- `activity.delete` - Activity deleted
354+
355+
- **Athlete Events:**
356+
- `athlete.update` - Athlete profile updated
357+
358+
For more information about webhook events, see the [Strava Webhook Documentation](https://developers.strava.com/docs/webhooks/).
359+
216360
## UML diagrams
217361
### Class diagram
218362
![class](https://cloud.githubusercontent.com/assets/1196963/4705696/764cd4e2-587e-11e4-8c9f-d265255ee0a2.png)

examples/webhook-endpoint.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
require_once __DIR__ . '/../vendor/autoload.php';
4+
5+
use Strava\API\Webhook;
6+
7+
/**
8+
* Complete Strava Webhook Endpoint
9+
*
10+
* This is a complete webhook endpoint that handles both subscription
11+
* verification (GET requests) and event processing (POST requests).
12+
*
13+
* Usage:
14+
* 1. Upload this file to your web server
15+
* 2. Set the $verifyToken to match what you use when creating subscriptions
16+
* 3. Configure your Strava app to use this URL as the webhook callback
17+
* 4. Implement your event handling logic in the $eventHandler function
18+
*/
19+
20+
// Configuration
21+
$verifyToken = 'your_verify_token_here'; // Must match the token used when creating subscriptions
22+
23+
// Note: Logging is not included in the library to keep it framework-agnostic.
24+
// You can add your own logging in your application if needed.
25+
26+
// Define your event handler function (matching your tested implementation)
27+
$eventHandler = function($event) {
28+
// Log the event
29+
error_log('Processing Strava webhook event: ' . json_encode($event, JSON_PRETTY_PRINT));
30+
31+
// Validate payload structure (matching your tested implementation)
32+
$validation = Webhook::validateEventPayload($event);
33+
if (!$validation['valid']) {
34+
error_log('Invalid webhook payload structure: ' . $validation['error']);
35+
return false;
36+
}
37+
38+
// Only process activity creation events (matching your business logic)
39+
if (Webhook::isActivityCreationEvent($event)) {
40+
$activityId = Webhook::getObjectId($event);
41+
$athleteId = Webhook::getAthleteId($event);
42+
43+
error_log("Processing activity creation - Activity ID: {$activityId}, Athlete ID: {$athleteId}");
44+
45+
// Your custom logic for new activity
46+
// Example: Import activity, update database, send notifications, etc.
47+
48+
return true; // Indicate successful processing
49+
} else {
50+
// Log other event types but don't process them (matching your implementation)
51+
$eventType = Webhook::getEventType($event);
52+
error_log("Skipping non-activity creation event: {$eventType}");
53+
return true; // Not an error, just not what we're interested in
54+
}
55+
};
56+
57+
// Handle the webhook request
58+
Webhook::handleWebhookEndpoint($verifyToken, $eventHandler);
59+
60+
// Note: The handleWebhookEndpoint method will exit after sending the response,
61+
// so this line will never be reached.

examples/webhook-example.php

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
require_once __DIR__ . '/../vendor/autoload.php';
4+
5+
use Strava\API\Client;
6+
use Strava\API\Service\REST;
7+
use Strava\API\Webhook;
8+
use GuzzleHttp\Client as GuzzleClient;
9+
10+
/**
11+
* Example: Strava Webhook Integration
12+
*
13+
* This example demonstrates how to use the StravaPHP library
14+
* to manage webhook subscriptions and handle webhook events.
15+
*/
16+
17+
// Configuration - Replace with your actual values
18+
$clientId = 12345; // Your Strava app client ID
19+
$clientSecret = 'your_client_secret_here'; // Your Strava app client secret
20+
$callbackUrl = 'https://yourdomain.com/webhook-endpoint.php'; // Your webhook endpoint URL
21+
$verifyToken = Webhook::generateVerifyToken(); // Generate a random verify token
22+
23+
// Note: Logging is not included in the library to keep it framework-agnostic.
24+
// You can add your own logging in your application if needed.
25+
26+
// Create API client (you'll need an access token for some operations)
27+
$accessToken = 'your_access_token_here'; // Get this through OAuth flow
28+
$adapter = new GuzzleClient(['base_uri' => 'https://www.strava.com/api/v3/']);
29+
$service = new REST($accessToken, $adapter);
30+
$client = new Client($service);
31+
32+
try {
33+
// 1. Create a webhook subscription
34+
echo "Creating webhook subscription...\n";
35+
$subscription = $client->createWebhookSubscription(
36+
$clientId,
37+
$clientSecret,
38+
$callbackUrl,
39+
$verifyToken
40+
);
41+
42+
echo "Subscription created successfully!\n";
43+
echo "Subscription ID: " . $subscription['id'] . "\n";
44+
echo "Callback URL: " . $subscription['callback_url'] . "\n";
45+
echo "Created at: " . $subscription['created_at'] . "\n\n";
46+
47+
// 2. List existing webhook subscriptions
48+
echo "Listing webhook subscriptions...\n";
49+
$subscriptions = $client->listWebhookSubscriptions($clientId, $clientSecret);
50+
51+
echo "Found " . count($subscriptions) . " subscription(s):\n";
52+
foreach ($subscriptions as $sub) {
53+
echo "- ID: " . $sub['id'] . ", URL: " . $sub['callback_url'] . "\n";
54+
}
55+
echo "\n";
56+
57+
// 3. Delete a webhook subscription (uncomment to use)
58+
/*
59+
echo "Deleting webhook subscription...\n";
60+
$deleted = $client->deleteWebhookSubscription(
61+
$clientId,
62+
$clientSecret,
63+
$subscription['id']
64+
);
65+
66+
if ($deleted) {
67+
echo "Subscription deleted successfully!\n";
68+
} else {
69+
echo "Failed to delete subscription.\n";
70+
}
71+
*/
72+
73+
} catch (Exception $e) {
74+
echo "Error: " . $e->getMessage() . "\n";
75+
}
76+
77+
/**
78+
* Example webhook endpoint handler
79+
*
80+
* Save this as webhook-endpoint.php on your server
81+
*/
82+
function webhookEndpointExample()
83+
{
84+
// Handle subscription challenge
85+
$verifyToken = 'your_verify_token_here'; // Same token used when creating subscription
86+
87+
$challengeResult = Webhook::handleSubscriptionChallenge($verifyToken);
88+
89+
if ($challengeResult['success']) {
90+
// Send challenge response to Strava
91+
Webhook::sendChallengeResponse($challengeResult['challenge']);
92+
} else {
93+
// Handle challenge error
94+
http_response_code(400);
95+
echo json_encode(['error' => $challengeResult['error']]);
96+
exit;
97+
}
98+
99+
// Process webhook events
100+
$eventResult = Webhook::processEvent();
101+
102+
if (!$eventResult['success']) {
103+
http_response_code(400);
104+
echo json_encode(['error' => $eventResult['error']]);
105+
exit;
106+
}
107+
108+
$event = $eventResult['event'];
109+
110+
// Handle different event types
111+
switch (Webhook::getEventType($event)) {
112+
case 'activity.create':
113+
echo "New activity created: " . $event['object_id'] . "\n";
114+
// Process new activity
115+
break;
116+
117+
case 'activity.update':
118+
echo "Activity updated: " . $event['object_id'] . "\n";
119+
// Process activity update
120+
break;
121+
122+
case 'activity.delete':
123+
echo "Activity deleted: " . $event['object_id'] . "\n";
124+
// Process activity deletion
125+
break;
126+
127+
case 'athlete.update':
128+
echo "Athlete updated: " . $event['object_id'] . "\n";
129+
// Process athlete update
130+
break;
131+
132+
default:
133+
echo "Unknown event type: " . Webhook::getEventType($event) . "\n";
134+
}
135+
136+
// Send success response
137+
http_response_code(200);
138+
echo json_encode(['status' => 'success']);
139+
}
140+
141+
// Uncomment to test the webhook endpoint
142+
// webhookEndpointExample();

0 commit comments

Comments
 (0)