PHP webhook receiver (web/github.php) that validates GitHub event signatures, routes by event type, logs to log/, and POSTs to Rocket Chat + Microsoft Teams.
composer install
vendor/bin/phpunit
vendor/bin/phpstan analyse
vendor/bin/php-cs-fixer fix --dry-run
vendor/bin/php-cs-fixer fix- Entry:
web/github.php(active) ·web/github-old.php(legacy, RC-only) - Core:
src/GithubWebhook.php·src/GithubMessageBuilder.php·src/IgnoredEventException.php·src/NotImplementedException.php - Config:
src/config.php(gitignored) — definesGITHUB_WEBHOOKS_SECRETand$chatChannels['rocketchat']/$chatChannels['teams'] - Logs:
log/— JSON files namedYmd_His_eventtype_action_user_repo.json - Tests:
tests/viaphpunit.xml· fixtures intests/events/{event_name}/ - Quality:
phpstan.neon·phpstan-bootstrap.php·.php-cs-fixer.dist.php(PSR2 + PHP74Migration) - CI:
.github/— GitHub Actions workflows (.github/workflows/ci.yml)
All event handling lives in web/github.php inside a switch ($EventType) block:
// 1. Validate signature — always first
if (!$Hook->ValidateHubSignature(GITHUB_WEBHOOKS_SECRET)) {
throw new Exception('Secret validation failed.');
}
// 2. Log every event to log/ before processing
file_put_contents(__DIR__.'/../log/'.date('Ymd_His').'_'.$EventType
.(isset($Message['action']) ? '_'.$Message['action'] : '')
.'_'.$User.'_'.str_replace(['/', '-', ' '], '_', $RepositoryName).'.json',
json_encode($log, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
// 3. Route by event type — $Builder->build() called before switch
switch ($EventType) {
case 'push': /* ... */ SendToChat('notifications', $Msg, $useRC, $useTeams); break;
default: SendToChat('notifications', $Msg, $useRC, $useTeams); break;
}SendToChat(string $Where, array $Payload, bool $useRC = true, bool $useTeams = true): bool$Wheremaps to$chatChannels['rocketchat'][$Where]and$chatChannels['teams'][$Where]- Teams payload wraps
$Payload['text']in['type' => 'message', 'message' => ...] - Always set
$useRC/$useTeamsflags per-case; some repos skip Teams (seeissuescase)
| Exception | HTTP Code | Meaning |
|---|---|---|
IgnoredEventException |
200 | fork, watch, status — ignored by design |
NotImplementedException |
501 | unhandled event type |
Exception |
500 (default) | signature fail, empty payload, etc. |
Note: README lists
check_run,check_suite,workflow_run,workflow_jobas unsupported — they ARE handled inweb/github.php. The README event lists are stale.
Each event type has a directory under tests/events/{event_name}/ containing:
payload.json— raw GitHub webhook payloadtype.txt— event name (e.g.tests/events/commit_comment/type.txt)expected_text.txt— expected chat message text output fromGithubMessageBuilder
declare(strict_types=1)at top of every PHP file- PascalCase for variables from
$Hook(e.g.$RepositoryName,$EventType,$Message) JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHESon alljson_encodecalls inweb/github.php- PHP CS Fixer enforces
@PSR2+@PHP74Migration; run before committing
Run caliber refresh before creating git commits to keep docs in sync with code changes.
After it completes, stage any modified doc files before committing:
caliber refresh && git add CLAUDE.md .claude/ .cursor/ .github/copilot-instructions.md AGENTS.md CALIBER_LEARNINGS.md 2>/dev/nullRead CALIBER_LEARNINGS.md for patterns and anti-patterns learned from previous sessions.
These are auto-extracted from real tool usage — treat them as project-specific rules.