spatie/laravel-github-webhooks
Handle GitHub webhooks in Laravel: verify signatures, log valid calls, and dispatch jobs/events per webhook type. Includes a GitHubWebhookCall model to access payloads and queueable handlers for event-driven integrations.
Installation:
composer require spatie/laravel-github-webhooks
php artisan vendor:publish --provider="Spatie\GitHubWebhooks\GitHubWebhooksServiceProvider" --tag="github-webhooks-config"
php artisan vendor:publish --provider="Spatie\GitHubWebhooks\GitHubWebhooksServiceProvider" --tag="github-webhooks-migrations"
php artisan migrate
Configure GitHub Webhook:
GITHUB_WEBHOOK_SECRET in .env (found in GitHub repo settings).config/github-webhooks.php:
'signing_secret' => env('GITHUB_WEBHOOK_SECRET'),
'jobs' => [
'issues.opened' => \App\Jobs\GitHubWebhooks\HandleIssueOpened::class,
],
Route Setup (in routes/api.php):
Route::githubWebhooks('github-webhook');
First Use Case:
Create a job to handle a specific event (e.g., issues.opened):
namespace App\Jobs\GitHubWebhooks;
use Illuminate\Bus\Queueable;
use Spatie\GitHubWebhooks\Models\GitHubWebhookCall;
class HandleIssueOpened implements ShouldQueue {
use Queueable;
public function __construct(public GitHubWebhookCall $webhookCall) {}
public function handle() {
$issue = $this->webhookCall->payload('issue');
// Process issue (e.g., notify team, update DB)
}
}
Job-Based Handling:
config/github-webhooks.php for specific events (e.g., pull_request.closed).* as a wildcard for generic handlers (e.g., log all events).'jobs' => [
'pull_request.*' => \App\Jobs\GitHubWebhooks\HandlePR::class,
'*' => \App\Jobs\GitHubWebhooks\LogWebhook::class,
],
Event Listeners:
EventServiceProvider for real-time reactions:
protected $listen = [
'github-webhooks::push' => \App\Listeners\SyncRepo::class,
];
Payload Access:
$actor = $webhookCall->payload('sender.login');
$repo = $webhookCall->payload('repository.full_name');
Queue Optimization:
200 response within seconds).class HandleWebhook implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable;
public function handle() { /* ... */ }
}
Development Workflow:
'verify_signature' => false).Production Workflow:
'verify_signature' => true).github_webhook_calls table.Debugging:
\Log::info('Webhook payload:', $webhookCall->payload());
Integration with Other Packages:
spatie/laravel-activitylog to track changes:
Activity::log('github_webhook', 'Processed ' . $webhookCall->eventActionName());
Signature Mismatches:
GITHUB_WEBHOOK_SECRET causes WebhookFailed exceptions..env.Queue Failures:
fail() in jobs to log errors:
try { /* ... */ } catch (\Exception $e) {
\Log::error($e);
$this->fail($e);
}
Payload Parsing:
if (!isset($webhookCall->payload('action'))) {
throw new \InvalidArgumentException('Invalid payload');
}
Route Conflicts:
web.php.VerifyCsrfToken middleware:
protected $except = ['github-webhook'];
Inspect Raw Payloads:
php artisan tinker
>>> $webhook = \Spatie\GitHubWebhooks\Models\GitHubWebhookCall::find(1);
>>> $webhook->payload();
Check Headers:
X-GitHub-Event and X-Hub-Signature-256 headers:
$event = $webhookCall->headers()->get('X-GitHub-Event');
$signature = $webhookCall->headers()->get('X-Hub-Signature-256');
Retry Failed Webhooks:
php artisan queue:work --once
ProcessGitHubWebhookJob::dispatch($webhookCall);
Custom Models:
GitHubWebhookCall to add fields (e.g., processed_at):
class CustomWebhookCall extends GitHubWebhookCall {
protected $casts = ['processed_at' => 'datetime'];
}
config/github-webhooks.php:
'model' => \App\Models\CustomWebhookCall::class,
Custom Profiles:
WebhookProfile:
class CustomProfile implements WebhookProfile {
public function shouldProcess(GitHubWebhookCall $call) {
return $call->payload('repository.full_name') === 'your/repo';
}
}
'profile' => \App\Profiles\CustomProfile::class,
Webhook Retries:
class RetryJob extends ProcessGitHubWebhookJob {
public function handle() {
if ($this->attempts() > 3) {
\Log::error('Max retries reached');
return;
}
parent::handle();
}
}
Testing:
$payload = json_encode(['action' => 'opened']);
$this->postJson('/github-webhook', $payload, [
'HTTP_X_GITHUB_EVENT' => 'issues',
'HTTP_X_HUB_SIGNATURE_256' => 'sha256=' . hash_hmac('sha256', $payload, config('github-webhooks.signing_secret')),
]);
How can I help you explore Laravel packages today?