Skip to content

Add webhook endpoint for gate certification intake#1

Merged
github-actions[bot] merged 5 commits intomasterfrom
feature/webhook-gate-intake
Dec 15, 2025
Merged

Add webhook endpoint for gate certification intake#1
github-actions[bot] merged 5 commits intomasterfrom
feature/webhook-gate-intake

Conversation

@jordanpartridge
Copy link
Contributor

@jordanpartridge jordanpartridge commented Dec 15, 2025

Summary

  • Adds POST /api/webhooks/gate endpoint to receive certification results from gate workflows
  • Stores events using Laravel Verbs CertificationCompleted event
  • Validates required fields: repository, sha, verdict (approved/rejected/escalate)

Test Plan

  • Webhook accepts valid gate payload
  • Rejects missing required fields
  • Rejects invalid verdict values
  • Stores CertificationCompleted event in verb_events table

All tests use describe()/it() spec pattern per project conventions.

Files Changed

  • app/Events/CertificationCompleted.php - Verbs event for certification results
  • app/Http/Controllers/WebhookController.php - Webhook handler
  • app/Http/Requests/GateWebhookRequest.php - Validation rules
  • routes/api.php - API route definition
  • tests/Feature/WebhookGateTest.php - Spec tests

Summary by CodeRabbit

  • New Features

    • Added a webhook endpoint to accept certification completion notifications and emit a certification event.
    • Input validation for repository, commit SHA, verdict (approved/rejected/escalate) and optional reason, checks, triggered-by, and PR number.
  • Tests

    • Added feature tests for the webhook endpoint and updated unit/feature test structure.
  • Chore

    • Enabled API routing and adjusted bootstrap routing.
  • Style

    • Simplified the default welcome page to a minimal static landing.

✏️ Tip: You can customize this high-level summary in your review settings.

- POST /api/webhooks/gate receives certification results
- CertificationCompleted Verbs event stores all payloads
- GateWebhookRequest validates repository, sha, verdict
- Tests use describe()/it() spec pattern
- 100% coverage on new code
@coderabbitai
Copy link

coderabbitai bot commented Dec 15, 2025

Warning

Rate limit exceeded

@jordanpartridge has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 21 minutes and 26 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between f428519 and 9801ff0.

📒 Files selected for processing (4)
  • .env.testing (1 hunks)
  • app/Models/User.php (0 hunks)
  • database/factories/UserFactory.php (0 hunks)
  • database/migrations/0001_01_01_000000_create_users_table.php (0 hunks)

Walkthrough

Adds a webhook integration: request validation, controller endpoint, and event class for certification payloads; registers an API route and updates bootstrap routing; includes feature and unit test additions and a simplified welcome view replacement.

Changes

Cohort / File(s) Summary
Webhook Gate Implementation
app/Events/CertificationCompleted.php, app/Http/Controllers/WebhookController.php, app/Http/Requests/GateWebhookRequest.php, routes/api.php, bootstrap/app.php
New CertificationCompleted event with promoted public properties. New GateWebhookRequest FormRequest with validation rules (repository, sha, verdict enum, optional reason/checks/triggered_by/pr_number). New WebhookController::gate endpoint fires the event and returns { "status": "accepted" }. API route POST /api/webhooks/gate added and bootstrap routing updated to include routes/api.php.
Tests & Test bootstrap
tests/Feature/WebhookGateTest.php, tests/Feature/ExampleTest.php, tests/Unit/ExampleTest.php, tests/Pest.php
New feature tests for the webhook gate covering success, validation errors, and event storage. Existing example tests refactored to describe/it with declare(strict_types=1). tests/Pest.php enables the RefreshDatabase test extension.
UI / Welcome View
resources/views/welcome.blade.php
Replaces the dynamic Laravel welcome view with a minimal static HTML landing snippet titled "Synapse Sentinel Core".

Sequence Diagram

sequenceDiagram
    participant Client as Webhook Client
    participant Router as API Router
    participant Validator as GateWebhookRequest
    participant Controller as WebhookController
    participant Event as CertificationCompleted
    participant ClientResp as HTTP Response

    Client->>Router: POST /api/webhooks/gate
    Router->>Validator: Validate payload
    alt valid
        Validator->>Controller: call gate(validatedData)
        Controller->>Event: fire CertificationCompleted(validatedData)
        Event-->>Controller: dispatched
        Controller->>ClientResp: return {"status":"accepted"}
        ClientResp-->>Client: 200 OK
    else invalid
        Validator-->>ClientResp: 422 Unprocessable Entity (errors)
        ClientResp-->>Client: 422
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review focus areas:
    • GateWebhookRequest verdict enum and nullable fields.
    • Correct mapping of validated keys to CertificationCompleted constructor (naming: triggered_bytriggeredBy, pr_numberprNumber).
    • Event dispatch usage and any side effects (database verb events asserted in tests).
    • bootstrap routing change to ensure no routing conflicts.
    • Welcome view replacement for unintended removals.

Poem

🐰 A little rabbit sent a ping,
Through gates of code it made a spring,
Validated carrots, checks in tow,
An event hops out—onward we go! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a webhook endpoint for gate certification intake, which aligns with the PR objectives and the primary files modified.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/Feature/WebhookGateTest.php (1)

9-75: Good coverage; consider tightening the persistence assertion

The suite nicely covers success, required-field validation, verdict validation, and that an event record exists in verb_events. If you want extra safety later, you could extend the final assertDatabaseHas to also check one or two key payload fields (e.g., repository or verdict) once you settle on the JSON column/paths, but it’s not strictly necessary for this PR.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa88e5a and 40f9de6.

📒 Files selected for processing (8)
  • app/Events/CertificationCompleted.php (1 hunks)
  • app/Http/Controllers/WebhookController.php (1 hunks)
  • app/Http/Requests/GateWebhookRequest.php (1 hunks)
  • bootstrap/app.php (1 hunks)
  • routes/api.php (1 hunks)
  • tests/Feature/ExampleTest.php (1 hunks)
  • tests/Feature/WebhookGateTest.php (1 hunks)
  • tests/Unit/ExampleTest.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
routes/api.php (1)
app/Http/Controllers/WebhookController.php (1)
  • WebhookController (11-27)
app/Http/Controllers/WebhookController.php (3)
app/Events/CertificationCompleted.php (1)
  • CertificationCompleted (9-20)
app/Http/Requests/GateWebhookRequest.php (1)
  • GateWebhookRequest (10-32)
app/Http/Controllers/Controller.php (1)
  • Controller (5-8)
🔇 Additional comments (6)
app/Events/CertificationCompleted.php (1)

11-19: Typed event payload looks good

Constructor promotion with precise types for the webhook fields is clear and matches the request validation; no functional issues here.

routes/api.php (1)

5-9: Route wiring is straightforward and consistent

The POST route cleanly targets WebhookController::gate with a sensible name; nothing blocking here.

tests/Feature/ExampleTest.php (1)

5-16: Describe/it structure and coverage look fine

Splitting into explicit homepage and health-check cases makes intent clear; the assertions are appropriate.

tests/Unit/ExampleTest.php (1)

5-8: Unit example test is a clean Pest template

Minimal describe/it structure is fine as a baseline unit-test example.

bootstrap/app.php (1)

10-12: API routes correctly wired into the application bootstrap

Hooking routes/api.php into withRouting is necessary for the webhook endpoint to be reachable; this change looks correct.

app/Http/Controllers/WebhookController.php (1)

13-26: Field mapping and response semantics look correct

Using named arguments from $request->validated() keeps the mapping to CertificationCompleted explicit and hard to mis-order, and returning a simple {'status': 'accepted'} JSON payload for a successful intake is reasonable for this webhook.

Comment on lines +12 to +30
public function authorize(): bool
{
return true;
}

/**
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'repository' => ['required', 'string'],
'sha' => ['required', 'string'],
'verdict' => ['required', Rule::in(['approved', 'rejected', 'escalate'])],
'reason' => ['nullable', 'string'],
'checks' => ['nullable', 'array'],
'triggered_by' => ['nullable', 'string'],
'pr_number' => ['nullable', 'integer'],
];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, verify the file exists and check its actual content
find . -name "GateWebhookRequest.php" -type f

Repository: synapse-sentinel/core

Length of output: 107


🏁 Script executed:

# Check if the file exists at the specified path and show its content
if [ -f "app/Http/Requests/GateWebhookRequest.php" ]; then
  echo "=== File found. Content:"; 
  cat -n "app/Http/Requests/GateWebhookRequest.php"
else
  echo "File not found at app/Http/Requests/GateWebhookRequest.php"
  # Search for it
  find . -name "*.php" -path "*/Requests/*" | grep -i webhook || echo "No webhook request files found"
fi

Repository: synapse-sentinel/core

Length of output: 1086


🏁 Script executed:

# Look for the route definition for this webhook endpoint
# Check common route files
echo "=== Checking routes/web.php:"; 
if [ -f "routes/web.php" ]; then rg -i "gatewebhook|webhook.*gate" routes/web.php -A 5; fi
echo ""
echo "=== Checking routes/api.php:"; 
if [ -f "routes/api.php" ]; then rg -i "gatewebhook|webhook.*gate" routes/api.php -A 5; fi
echo ""
echo "=== Checking all route files:"; 
find routes -name "*.php" -type f 2>/dev/null | xargs rg -i "gatewebhook|webhook.*gate" -A 5 2>/dev/null || echo "No matches found"

Repository: synapse-sentinel/core

Length of output: 374


🏁 Script executed:

# Search for any signature validation or auth mechanisms related to webhooks
echo "=== Searching for signature/HMAC validation:"; 
rg -i "signature|hmac|X-Hub-Signature" --type php | head -20
echo ""
echo "=== Searching for webhook auth patterns:"; 
rg -i "webhook.*auth|webhook.*token|webhook.*secret" --type php | head -20

Repository: synapse-sentinel/core

Length of output: 151


🏁 Script executed:

# Check for middleware that might be protecting the webhook route
echo "=== Looking for webhook-related middleware:"; 
find app/Http/Middleware -name "*.php" -type f 2>/dev/null | xargs rg -l "webhook" 2>/dev/null || echo "No webhook middleware found"
echo ""
echo "=== Searching for VerifyWebhook or similar patterns:"; 
rg "VerifyWebhook|verify.*webhook|webhook.*middleware" --type php

Repository: synapse-sentinel/core

Length of output: 221


🏁 Script executed:

# Search for any controller handling this request to see if there are additional checks
echo "=== Looking for controllers that might handle webhook requests:"; 
find app/Http/Controllers -name "*Webhook*.php" -o -name "*Gate*.php" | head -10

Repository: synapse-sentinel/core

Length of output: 171


🏁 Script executed:

# Check the full route definition with middleware
echo "=== Full routes/api.php around webhook route:"; 
rg -B 5 -A 10 "webhooks/gate" routes/api.php

Repository: synapse-sentinel/core

Length of output: 314


🏁 Script executed:

# Examine WebhookController to see how the webhook is processed
echo "=== WebhookController content:"; 
wc -l app/Http/Controllers/WebhookController.php

Repository: synapse-sentinel/core

Length of output: 141


🏁 Script executed:

# Read the WebhookController
head -100 app/Http/Controllers/WebhookController.php

Repository: synapse-sentinel/core

Length of output: 859


🏁 Script executed:

# Look at the test file to understand expected security behavior
echo "=== WebhookGateTest content (first 150 lines):"; 
head -150 tests/Feature/WebhookGateTest.php

Repository: synapse-sentinel/core

Length of output: 2335


🏁 Script executed:

# Search for any signature validation or custom middleware that might be applied
echo "=== Searching for signature or verification in config or middleware:"; 
find app/Http/Middleware -name "*.php" -type f 2>/dev/null | xargs cat 2>/dev/null

Repository: synapse-sentinel/core

Length of output: 133


🏁 Script executed:

# Check if there's a bootstrap or service provider that might register middleware
echo "=== Checking for webhook verification in service providers:"; 
rg -i "webhook|signature|verify" app/Providers --type php -A 3

Repository: synapse-sentinel/core

Length of output: 124


Add authentication to the webhook endpoint; currently any caller can submit certification verdicts

The authorize() method returns true unconditionally, the route has no middleware or auth guard, and no signature/HMAC validation exists anywhere in the codebase. This means any caller with network access can submit "approved" verdicts for any repository and commit, which is a critical security risk if this endpoint drives production gating decisions.

Implement one of:

  • Signature verification (e.g., validate X-Hub-Signature HMAC against a shared secret in middleware before the route is reached).
  • API token / bearer token authentication via an auth guard.
  • If network isolation is already in place (firewall restricting access to trusted infrastructure only), document this assumption explicitly in code or a security note.
🤖 Prompt for AI Agents
app/Http/Requests/GateWebhookRequest.php lines 12-30: authorize() currently
returns true allowing any caller to submit verdicts; add authentication by
verifying a shared secret signature or bearer token before the controller
handles the request. Implement middleware that reads the incoming request body
and the signature header (e.g., X-Hub-Signature / X-Signature) or Authorization
bearer token, compute an HMAC using a secret stored in env/config, compare it
using a timing-safe comparison, and abort with 401/403 on mismatch; register
that middleware on the webhook route (or wire it into route middleware group)
and keep authorize() conservative (return false or only true when the request
user/context is authenticated) or remove unconditional true so only
authenticated requests proceed; alternatively, if network isolation is
guaranteed, add a documented comment and a runtime check that ensures the
expected network constraint or env flag is set.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
resources/views/welcome.blade.php (1)

3-11: Minimal, clear welcome view looks good

Static head/body markup is valid, matches the app name, and avoids unnecessary assets/logic. This is a reasonable simplification for a core/event-intake service landing page.

If you ever care about external visibility, you could optionally add a <meta name="description"> summarizing the service, but not required for this PR.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40f9de6 and f428519.

📒 Files selected for processing (3)
  • resources/views/welcome.blade.php (1 hunks)
  • tests/Feature/WebhookGateTest.php (1 hunks)
  • tests/Pest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/Feature/WebhookGateTest.php
🔇 Additional comments (1)
tests/Pest.php (1)

14-16: LGTM! Enabling RefreshDatabase for Feature tests.

Uncommenting the RefreshDatabase trait is the correct approach for the new webhook feature tests that interact with the database. This ensures test isolation and a clean database state for each test run.

@github-actions github-actions bot merged commit 15f1ff1 into master Dec 15, 2025
1 check passed
@github-actions github-actions bot deleted the feature/webhook-gate-intake branch December 15, 2025 04:37
@coderabbitai coderabbitai bot mentioned this pull request Dec 15, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant