- Add package name validation with strict regex patterns - Convert all Process::run() calls to array syntax - Support Composer and NPM package name formats - Add comprehensive shell injection tests (20 attack patterns) - Update security docs and changelog Fixes P2 shell injection vulnerability from security audit. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
306 lines
7.1 KiB
Markdown
306 lines
7.1 KiB
Markdown
---
|
|
title: Webhooks
|
|
description: Webhook configuration and integration guide
|
|
updated: 2026-01-29
|
|
---
|
|
|
|
# Webhooks
|
|
|
|
The Uptelligence module can receive webhooks from vendor release systems to automatically track new versions.
|
|
|
|
## Supported Providers
|
|
|
|
| Provider | Event Types | Signature Method |
|
|
|----------|-------------|------------------|
|
|
| GitHub | Release published/created | HMAC-SHA256 |
|
|
| GitLab | Release created, tag push | Token header |
|
|
| npm | Package published | HMAC-SHA256 |
|
|
| Packagist | Package updated | HMAC-SHA1 |
|
|
| Custom | Flexible | HMAC-SHA256 |
|
|
|
|
## Endpoint URL
|
|
|
|
Each webhook has a unique endpoint:
|
|
|
|
```
|
|
POST /api/uptelligence/webhook/{uuid}
|
|
```
|
|
|
|
The UUID is generated when the webhook is created and serves as the identifier.
|
|
|
|
## Configuring Webhooks
|
|
|
|
### GitHub
|
|
|
|
1. Go to repository Settings > Webhooks > Add webhook
|
|
2. Set Payload URL to: `https://your-domain.com/api/uptelligence/webhook/{uuid}`
|
|
3. Set Content type to: `application/json`
|
|
4. Set Secret to the webhook's secret (visible in admin panel)
|
|
5. Select "Let me select individual events"
|
|
6. Check "Releases" only
|
|
7. Save webhook
|
|
|
|
**Expected headers:**
|
|
- `X-Hub-Signature-256: sha256={signature}`
|
|
- `X-GitHub-Event: release`
|
|
|
|
### GitLab
|
|
|
|
1. Go to project Settings > Webhooks
|
|
2. Set URL to: `https://your-domain.com/api/uptelligence/webhook/{uuid}`
|
|
3. Set Secret token to the webhook's secret
|
|
4. Check "Releases events" and optionally "Tag push events"
|
|
5. Save webhook
|
|
|
|
**Expected headers:**
|
|
- `X-Gitlab-Token: {secret}`
|
|
- `X-Gitlab-Event: Release Hook` or `Tag Push Hook`
|
|
|
|
### npm
|
|
|
|
npm webhooks are configured per package via the npm CLI or website.
|
|
|
|
```bash
|
|
npm hook add @scope/package https://your-domain.com/api/uptelligence/webhook/{uuid} {secret}
|
|
```
|
|
|
|
**Expected headers:**
|
|
- `X-Npm-Signature: {signature}`
|
|
|
|
### Packagist
|
|
|
|
Packagist webhooks are configured in the package settings on packagist.org.
|
|
|
|
1. Go to package page > Edit
|
|
2. Add webhook URL: `https://your-domain.com/api/uptelligence/webhook/{uuid}`
|
|
3. Set secret if required
|
|
|
|
**Expected headers:**
|
|
- `X-Hub-Signature: sha1={signature}`
|
|
|
|
### Custom
|
|
|
|
For custom integrations, send a POST request with:
|
|
|
|
**Headers:**
|
|
- `Content-Type: application/json`
|
|
- `X-Signature: sha256={hmac_sha256_of_body}` (optional)
|
|
|
|
**Body:**
|
|
```json
|
|
{
|
|
"version": "1.2.3",
|
|
"tag_name": "v1.2.3",
|
|
"name": "Release Name",
|
|
"body": "Release notes...",
|
|
"prerelease": false,
|
|
"published_at": "2026-01-29T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
## Signature Verification
|
|
|
|
### HMAC-SHA256 (GitHub, npm, Custom)
|
|
|
|
```php
|
|
$expectedSignature = hash_hmac('sha256', $payload, $secret);
|
|
$valid = hash_equals($expectedSignature, $providedSignature);
|
|
```
|
|
|
|
The signature header may have a `sha256=` prefix which is stripped before comparison.
|
|
|
|
### Token Comparison (GitLab)
|
|
|
|
```php
|
|
$valid = hash_equals($secret, $providedToken);
|
|
```
|
|
|
|
Direct constant-time comparison of the token.
|
|
|
|
### HMAC-SHA1 (Packagist)
|
|
|
|
```php
|
|
$expectedSignature = hash_hmac('sha1', $payload, $secret);
|
|
$valid = hash_equals($expectedSignature, $providedSignature);
|
|
```
|
|
|
|
## Secret Management
|
|
|
|
### Creating a Webhook
|
|
|
|
When a webhook is created, a 64-character random secret is automatically generated.
|
|
|
|
### Rotating Secrets
|
|
|
|
Secrets can be rotated with a grace period:
|
|
|
|
```php
|
|
$webhook->rotateSecret();
|
|
```
|
|
|
|
This:
|
|
1. Moves current secret to `previous_secret`
|
|
2. Generates new 64-character secret
|
|
3. Sets `secret_rotated_at` to current time
|
|
|
|
During the grace period (default 24 hours), both old and new secrets are accepted.
|
|
|
|
### Regenerating Without Grace
|
|
|
|
To immediately invalidate the old secret:
|
|
|
|
```php
|
|
$webhook->regenerateSecret();
|
|
```
|
|
|
|
This:
|
|
1. Generates new secret
|
|
2. Clears `previous_secret`
|
|
3. Clears `secret_rotated_at`
|
|
|
|
## Delivery Processing
|
|
|
|
### Flow
|
|
|
|
1. **Receive** - Webhook received at endpoint
|
|
2. **Validate** - Check webhook is active, verify signature
|
|
3. **Log** - Create `UptelligenceWebhookDelivery` record
|
|
4. **Queue** - Dispatch `ProcessUptelligenceWebhook` job
|
|
5. **Parse** - Extract version and metadata from payload
|
|
6. **Process** - Create/update version release record
|
|
7. **Notify** - Send notifications to subscribed users
|
|
|
|
### Delivery Statuses
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| `pending` | Queued for processing |
|
|
| `processing` | Currently being processed |
|
|
| `completed` | Successfully processed |
|
|
| `failed` | Processing failed |
|
|
| `skipped` | Not a release event or unable to parse |
|
|
|
|
### Retry Logic
|
|
|
|
Failed deliveries are retried with exponential backoff:
|
|
|
|
- Attempt 1: Immediate
|
|
- Attempt 2: After 30 seconds
|
|
- Attempt 3: After 60 seconds (2^2 * 30)
|
|
- Attempt 4: After 120 seconds (2^3 * 30)
|
|
|
|
Maximum 3 retries by default.
|
|
|
|
## Circuit Breaker
|
|
|
|
To prevent continuous processing of failing webhooks:
|
|
|
|
- After 10 consecutive failures, the webhook is automatically disabled
|
|
- Status changes to "Circuit Open"
|
|
- Manual intervention required to re-enable
|
|
|
|
Reset the circuit breaker:
|
|
|
|
```php
|
|
$webhook->update(['is_active' => true, 'failure_count' => 0]);
|
|
```
|
|
|
|
## Monitoring
|
|
|
|
### Admin Dashboard
|
|
|
|
The Webhook Manager component shows:
|
|
- Webhook status (Active, Disabled, Circuit Open)
|
|
- Last received timestamp
|
|
- Failure count
|
|
- Recent deliveries with status
|
|
|
|
### Deliveries Log
|
|
|
|
Each delivery records:
|
|
- Event type
|
|
- Provider
|
|
- Version extracted
|
|
- Payload (full JSON)
|
|
- Parsed data (normalised)
|
|
- Source IP
|
|
- Signature status
|
|
- Processing time
|
|
- Error message (if failed)
|
|
|
|
## Rate Limiting
|
|
|
|
Webhooks are rate-limited to prevent abuse:
|
|
|
|
- **60 requests/minute per webhook UUID**
|
|
- **30 requests/minute per IP** (fallback for unknown webhooks)
|
|
|
|
Rate limit exceeded returns `429 Too Many Requests`.
|
|
|
|
## Testing Webhooks
|
|
|
|
### Test Endpoint
|
|
|
|
```
|
|
POST /api/uptelligence/webhook/{uuid}/test
|
|
```
|
|
|
|
Returns webhook configuration status without processing:
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"webhook_id": "uuid-here",
|
|
"vendor_id": 1,
|
|
"provider": "github",
|
|
"is_active": true,
|
|
"signature_status": "valid",
|
|
"has_secret": true
|
|
}
|
|
```
|
|
|
|
### Manual Testing
|
|
|
|
Use curl to test a webhook:
|
|
|
|
```bash
|
|
# Generate signature
|
|
SECRET="your-webhook-secret"
|
|
PAYLOAD='{"tag_name":"v1.0.0","name":"Test Release"}'
|
|
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
|
|
|
|
# Send request
|
|
curl -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Hub-Signature-256: sha256=$SIGNATURE" \
|
|
-d "$PAYLOAD" \
|
|
https://your-domain.com/api/uptelligence/webhook/{uuid}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### "Invalid signature" Response
|
|
|
|
1. Verify the secret matches between provider and webhook config
|
|
2. Check that the payload is sent as raw JSON (not form-encoded)
|
|
3. Ensure the signature algorithm matches the provider
|
|
4. Check for encoding issues (UTF-8 BOM, etc.)
|
|
|
|
### "Webhook disabled" Response
|
|
|
|
1. Check if circuit breaker has tripped (failure_count >= 10)
|
|
2. Verify `is_active` is true in database
|
|
3. Re-enable via admin panel or API
|
|
|
|
### Deliveries Not Processing
|
|
|
|
1. Check queue worker is running: `php artisan queue:work --queue=uptelligence-webhooks`
|
|
2. Check for failed jobs: `php artisan queue:failed`
|
|
3. Review delivery status in admin panel
|
|
|
|
### Missing Release Record
|
|
|
|
1. Verify the event type is supported (release/publish, not push)
|
|
2. Check parsed_data in delivery record
|
|
3. Ensure version extraction succeeded
|
|
4. Check if version already exists (duplicate webhooks are de-duplicated)
|