Webhooks¶
A webhook is an HTTP POST from BCP Portal to your endpoint when an event happens. An alternative to API polling.
Why¶
- Real-time finding delivery into your SIEM / ticketing / Slack.
- Automate reactions (e.g., on critical finding create a Jira ticket).
- Integrate with internal systems without polling.
Configuration¶
- Settings → Integrations → Webhooks → New.
- Enter:
- URL — where to POST.
- Events — which events to subscribe to (see below).
- Secret — for HMAC signature. Auto-generated, can be changed.
- Active — on/off.
- Save → the platform immediately sends a test ping to verify.
Event types¶
| Event | When it fires |
|---|---|
finding.created |
New finding |
finding.updated |
Status or detail change |
finding.resolved |
Closed as resolved or false_positive |
scan.completed |
Scan finished |
scan.failed |
Scan error |
report.generated |
Report generated |
member.added |
User added to organization |
member.removed |
User removed |
You can subscribe to specific events or all.
Payload¶
Standard format:
json
{
"event": "finding.created",
"timestamp": "2026-06-29T10:23:45Z",
"organization_id": "org_abc123",
"data": {
// event-specific payload
}
}
Example: finding.created¶
json
{
"event": "finding.created",
"timestamp": "2026-06-29T10:23:45Z",
"organization_id": "org_abc123",
"data": {
"id": "find_xyz789",
"severity": "critical",
"source": "github",
"title": "AWS Access Key in public repo",
"description": "Found AWS Access Key (AKIA...) in commit abc123 of repo example/test",
"data_source": "yourcompany.com",
"discovered_at": "2026-06-29T10:23:40Z",
"status": "new",
"url": "https://bcp.bytecode.team/findings/find_xyz789",
"external_url": "https://github.com/example/test/blob/abc123/file.env#L42",
"tags": ["aws", "production"]
}
}
Signature verification¶
Every webhook includes an X-BCP-Signature header with HMAC-SHA256 of the payload.
Python¶
```python import hmac import hashlib
def verify_signature(payload_body: bytes, signature_header: str, secret: str) -> bool: expected = hmac.new( secret.encode(), payload_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(f"sha256={expected}", signature_header) ```
Node.js¶
```javascript const crypto = require('crypto');
function verifySignature(payloadBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payloadBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(sha256=${expected}),
Buffer.from(signatureHeader)
);
}
```
⚠️ Use constant-time comparison (hmac.compare_digest / crypto.timingSafeEqual), not plain == — this protects against timing attacks.
Retry policy¶
If your endpoint returns a non-2xx:
- 3 attempts with exponential backoff: 1s, 10s, 60s.
- After 3 failures the webhook is marked failed and recorded in the Delivery log.
- After 10 consecutive failed deliveries the webhook is automatically deactivated, and an email is sent to the owner.
Delivery log¶
Settings → Integrations → Webhooks → {your webhook} → Delivery log — history of all deliveries with:
- Status (delivered / failed / retrying).
- HTTP response code.
- Latency.
- Payload (can be re-sent).
Kept for 30 days.
Best practices¶
- Respond fast — return 200 immediately, handle the payload asynchronously. If your endpoint takes > 10 seconds we count it as a timeout.
- Idempotency — the same webhook may arrive again on retry. Check
data.idandeventto avoid duplicating actions. - Verify signature — otherwise an attacker can forge a webhook and trick your systems.
- Logging — keep your own logs for troubleshooting.
Local testing¶
Use ngrok or a similar tunnel to test against a local endpoint.
Alternatively — webhook.site as a quick debug receiver.