Event-driven integration for real-time processing and monitoring.
Webhooks allow gnaw to send real-time notifications when specific events occur, enabling integration with external systems and services.
Triggered when a search operation completes.
Payload:
{
"event": "search.completed",
"timestamp": "2023-03-15T12:34:58Z",
"data": {
"search_term": "ERROR",
"path": "/var/log/app.log",
"result_count": 5,
"execution_time": "0.123s",
"options": {
"recursive": true,
"ignore_case": false
}
}
}
Triggered when a search operation fails.
Payload:
{
"event": "search.failed",
"timestamp": "2023-03-15T12:34:58Z",
"data": {
"search_term": "ERROR",
"path": "/var/log/app.log",
"error": {
"code": "FILE_NOT_FOUND",
"message": "File not found",
"details": "/var/log/app.log does not exist"
}
}
}
Triggered when a code index is successfully built.
Payload:
{
"event": "agent.index.built",
"timestamp": "2023-03-15T12:34:58Z",
"data": {
"target_dir": "/path/to/code",
"index_path": "/path/to/code/.gnaw/index",
"file_count": 1250,
"build_time": "45.2s",
"size_mb": 45.2
}
}
Triggered when index building fails.
Payload:
{
"event": "agent.index.failed",
"timestamp": "2023-03-15T12:34:58Z",
"data": {
"target_dir": "/path/to/code",
"error": {
"code": "PERMISSION_DENIED",
"message": "Insufficient permissions",
"details": "Cannot write to /path/to/code/.gnaw/"
}
}
}
Triggered when errors are detected in monitored files.
Payload:
{
"event": "monitor.error_detected",
"timestamp": "2023-03-15T12:34:58Z",
"data": {
"file_path": "/var/log/app.log",
"line_number": 42,
"line": "2023-03-15 12:34:58,789 ERROR django.request: Internal Server Error",
"severity": "ERROR",
"context": {
"before": "2023-03-15 12:34:56,456 INFO app.config: Loading configuration",
"after": "2023-03-15 12:34:59,012 INFO app.recovery: Attempting recovery"
}
}
}
Triggered when specific patterns are matched in monitored files.
Payload:
{
"event": "monitor.pattern_matched",
"timestamp": "2023-03-15T12:34:58Z",
"data": {
"pattern": "TODO",
"file_path": "src/main.rs",
"line_number": 15,
"line": "// TODO: Implement error handling",
"context": {
"before": "pub fn process_data() -> Result<(), Error> {",
"after": " Ok(())"
}
}
}
Create a new webhook.
Request:
{
"name": "Error Monitor",
"url": "https://your-app.com/webhooks/gnaw",
"events": ["monitor.error_detected", "monitor.pattern_matched"],
"secret": "your-webhook-secret",
"active": true,
"retry_count": 3,
"retry_delay": 5
}
Response:
{
"id": "webhook_123",
"name": "Error Monitor",
"url": "https://your-app.com/webhooks/gnaw",
"events": ["monitor.error_detected", "monitor.pattern_matched"],
"secret": "your-webhook-secret",
"active": true,
"retry_count": 3,
"retry_delay": 5,
"created_at": "2023-03-15T12:34:58Z",
"last_triggered": null
}
List all webhooks.
Response:
{
"webhooks": [
{
"id": "webhook_123",
"name": "Error Monitor",
"url": "https://your-app.com/webhooks/gnaw",
"events": ["monitor.error_detected"],
"active": true,
"created_at": "2023-03-15T12:34:58Z",
"last_triggered": "2023-03-15T12:35:12Z"
}
]
}
Update a webhook.
Request:
{
"name": "Updated Error Monitor",
"active": false
}
Delete a webhook.
Response:
{
"message": "Webhook deleted successfully"
}
All webhook payloads include a signature for verification:
# Generate signature
signature = HMAC-SHA256(payload, secret)
# Verify signature
expected_signature = HMAC-SHA256(payload, secret)
if signature == expected_signature:
# Valid webhook
else:
# Invalid webhook
X-Gnaw-Signature: sha256=abc123def456...
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected_signature = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
Failed webhook deliveries are automatically retried:
X-Gnaw-Retry-Count: 2
X-Gnaw-Retry-Delay: 10
X-Gnaw-Retry-After: 20
from flask import Flask, request
import hmac
import hashlib
import json
app = Flask(__name__)
@app.route('/webhooks/gnaw', methods=['POST'])
def gnaw_webhook():
# Verify signature
signature = request.headers.get('X-Gnaw-Signature')
if not verify_signature(request.data, signature):
return 'Unauthorized', 401
# Process webhook
data = request.json
event = data['event']
if event == 'monitor.error_detected':
error_data = data['data']
message = f"🚨 Error detected in {error_data['file_path']}: {error_data['line']}"
# Send to Slack
send_slack_message(message)
return 'OK', 200
def verify_signature(payload, signature):
secret = 'your-webhook-secret'
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
app.post('/webhooks/gnaw', (req, res) => {
// Verify signature
const signature = req.headers['x-gnaw-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Unauthorized');
}
// Process webhook
const { event, data } = req.body;
if (event === 'monitor.error_detected') {
const message = `🚨 Error detected in ${data.file_path}: ${data.line}`;
// Send to Discord
sendDiscordMessage(message);
}
res.status(200).send('OK');
});
function verifySignature(payload, signature) {
const secret = 'your-webhook-secret';
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
from flask import Flask, request
import sqlite3
import json
app = Flask(__name__)
@app.route('/webhooks/gnaw', methods=['POST'])
def gnaw_webhook():
data = request.json
event = data['event']
# Store in database
conn = sqlite3.connect('webhooks.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS webhook_events (
id INTEGER PRIMARY KEY,
event TEXT,
timestamp TEXT,
data TEXT
)
''')
cursor.execute('''
INSERT INTO webhook_events (event, timestamp, data)
VALUES (?, ?, ?)
''', (event, data['timestamp'], json.dumps(data['data'])))
conn.commit()
conn.close()
return 'OK', 200