-
-
Save Geovanek/dd3eb502d8be6582223a1c4df610c3eb to your computer and use it in GitHub Desktop.
Laravel Strava webhooks based on https://github.com/RichieMcMullen/laravel-strava/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
https://developers.strava.com/ | |
https://developers.strava.com/docs/webhooks/ | |
https://developers.strava.com/docs/webhookexample/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#... | |
STRAVA_PUSH_SUBSCRIPTIONS_URL="https://www.strava.com/api/v3/push_subscriptions" | |
# The app webhook callback URL that Strava uses to fire GET/POST events | |
STRAVA_WEBHOOK_CALLBACK_URL="https://www.yoursite.com/webhook" | |
# A random string generated by my app that Strava uses to verify the request | |
STRAVA_WEBHOOK_VERIFY_TOKEN=123ABCdefXYZ4567 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/Providers/AppServiceProvider.php | |
namespace App\Providers; | |
use App\Services\StravaWebhookService; | |
use Illuminate\Support\ServiceProvider; | |
class AppServiceProvider extends ServiceProvider | |
{ | |
public function register() | |
{ | |
$this->app->singleton(StravaWebhookService::class, function ($app) { | |
return new StravaWebhookService(); | |
}); | |
} | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// config/services.php | |
return [ | |
// ... | |
'strava' => [ | |
'push_subscriptions_url' => env('STRAVA_PUSH_SUBSCRIPTIONS_URL'), | |
'webhook_callback_url' => env('STRAVA_WEBHOOK_CALLBACK_URL'), | |
'webhook_verify_token' => env('STRAVA_WEBHOOK_VERIFY_TOKEN'), | |
], | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/Services/StravaWebhookService.php | |
namespace App\Services; | |
use Illuminate\Http\JsonResponse; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Facades\Http; | |
use Illuminate\Support\Facades\Log; | |
class StravaWebhookService | |
{ | |
private string $url; | |
private string $client_id; | |
private string $client_secret; | |
private string $callback_url; | |
private string $verify_token; | |
public function __construct() | |
{ | |
$this->url = config('services.strava.push_subscriptions_url'); | |
$this->client_id = config('ct_strava.client_id'); | |
$this->client_secret = config('ct_strava.client_secret'); | |
$this->callback_url = config('services.strava.webhook_callback_url'); | |
$this->verify_token = config('services.strava.webhook_verify_token'); | |
} | |
public function subscribe(): int|null | |
{ | |
$response = Http::post($this->url, [ | |
'client_id' => $this->client_id, | |
'client_secret' => $this->client_secret, | |
'callback_url' => $this->callback_url, | |
'verify_token' => $this->verify_token, | |
]); | |
if ($response->status() === Response::HTTP_CREATED) { | |
return json_decode($response->body())->id; | |
} | |
Log::channel('strava')->error(json_encode($response->body()), [$response->status()]); | |
return null; | |
} | |
public function unsubscribe(): bool | |
{ | |
$id = app(StravaWebhookService::class)->view(); // use the singleton | |
if (!$id) { | |
return false; | |
} | |
$response = Http::delete("$this->url/$id", [ | |
'client_id' => $this->client_id, | |
'client_secret' => $this->client_secret, | |
]); | |
if ($response->status() === Response::HTTP_NO_CONTENT) { | |
return true; | |
} | |
Log::channel('strava')->error(json_encode($response->body()), [$response->status()]); | |
return false; | |
} | |
public function view(): int|null | |
{ | |
$response = Http::get($this->url, [ | |
'client_id' => $this->client_id, | |
'client_secret' => $this->client_secret, | |
]); | |
if ($response->status() === Response::HTTP_OK) { | |
$body = json_decode($response->body()); | |
if ($body) { | |
return $body[0]->id; // each application can have only 1 subscription | |
} else { | |
return null; // no subscription found | |
} | |
} | |
Log::channel('strava')->error(json_encode($response->body()), [$response->status()]); | |
return null; | |
} | |
// GET https://mycallbackurl.com?hub.verify_token=STRAVA&hub.challenge=15f7d1a91c1f40f8a748fd134752feb3&hub.mode=subscribe | |
public function validate(string $mode, string $token, string $challenge): Response|JsonResponse | |
{ | |
// Checks if a token and mode is in the query string of the request | |
if ($mode && $token) { | |
// Verifies that the mode and token sent are valid | |
if ($mode === 'subscribe' && $token === $this->verify_token) { | |
// Responds with the challenge token from the request | |
return response()->json(['hub.challenge' => $challenge]); | |
} else { | |
// Responds with '403 Forbidden' if verify tokens do not match | |
return response('', Response::HTTP_FORBIDDEN); | |
} | |
} | |
return response('', Response::HTTP_FORBIDDEN); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/Console/Commands/SubscribeToStravaWebhookCommand.php | |
namespace App\Console\Commands; | |
use App\Services\StravaWebhookService; | |
use Illuminate\Console\Command; | |
class SubscribeToStravaWebhookCommand extends Command | |
{ | |
protected $signature = 'strava:subscribe'; | |
protected $description = 'Subscribes to a Strava webhook'; | |
public function __construct() | |
{ | |
parent::__construct(); | |
} | |
public function handle() | |
{ | |
$id = app(StravaWebhookService::class)->subscribe(); | |
if ($id) { | |
$this->info("Successfully subscribed ID: {$id}"); | |
} else { | |
$this->warn('Unable to subscribe'); | |
} | |
return 0; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/Console/Commands/UnsubscribeStravaWebhookCommand.php | |
namespace App\Console\Commands; | |
use App\Services\StravaWebhookService; | |
use Illuminate\Console\Command; | |
class UnsubscribeStravaWebhookCommand extends Command | |
{ | |
protected $signature = 'strava:unsubscribe'; | |
protected $description = 'Deletes a Strava webhook subscription'; | |
public function __construct() | |
{ | |
parent::__construct(); | |
} | |
public function handle() | |
{ | |
if (app(StravaWebhookService::class)->unsubscribe()) { | |
$this->info("Successfully unsubscribed"); | |
} else { | |
$this->warn('Error or no subscription found'); | |
} | |
return 0; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/Console/Commands/ViewStravaWebhookCommand.php | |
namespace App\Console\Commands; | |
use App\Services\StravaWebhookService; | |
use Illuminate\Console\Command; | |
class ViewStravaWebhookCommand extends Command | |
{ | |
protected $signature = 'strava:view-subscription'; | |
protected $description = 'Views a Strava webhook subscription'; | |
public function __construct() | |
{ | |
parent::__construct(); | |
} | |
public function handle() | |
{ | |
$id = app(StravaWebhookService::class)->view(); | |
if ($id) { | |
$this->info("Subscription ID: $id"); | |
} else { | |
$this->warn('Error or no subscription found'); | |
} | |
return 0; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// routes/web.php | |
Route::get('/webhook', function (Request $request) { | |
$mode = $request->query('hub_mode'); // hub.mode | |
$token = $request->query('hub_verify_token'); // hub.verify_token | |
$challenge = $request->query('hub_challenge'); // hub.challenge | |
return app(StravaWebhookService::class)->validate($mode, $token, $challenge); | |
}); | |
Route::post('/webhook', function (Request $request) { | |
$aspect_type = $request['aspect_type']; // "create" | "update" | "delete" | |
$event_time = $request['event_time']; // time the event occurred | |
$object_id = $request['object_id']; // activity ID | athlete ID | |
$object_type = $request['object_type']; // "activity" | "athlete" | |
$owner_id = $request['owner_id']; // athlete ID | |
$subscription_id = $request['subscription_id']; // push subscription ID receiving the event | |
$updates = $request['updates']; // activity update: {"title" | "type" | "private": true/false} ; app deauthorization: {"authorized": false} | |
Log::channel('strava')->info(json_encode($request->all())); | |
return response('EVENT_RECEIVED', Response::HTTP_OK); | |
})->withoutMiddleware(VerifyCsrfToken::class); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment