Webhooks
Receive real-time notifications when contexts are verified, confirmed, or when events occur in your BB-MCP applications. Set up reliable event-driven workflows.
How Webhooks Work
BB-MCP sends HTTP POST requests to your endpoints when events occur
Webhook Flow
- 1Event occurs in BB-MCP (context confirmed, etc.)
- 2BB-MCP sends POST request to your webhook URL
- 3Your application processes the event
- 4You respond with HTTP 200 to acknowledge receipt
Available Events
Setting Up Webhooks
Configure webhook endpoints using the API or dashboard
Using the Dashboard
Using the API
# Create webhook endpoint
POST https://api.quietstack.com/v1/webhooks
Authorization: Bearer your_api_key_here
Content-Type: application/json
{
"url": "https://your-app.com/webhooks/bbmcp",
"events": [
"context.created",
"context.confirmed",
"context.failed"
],
"description": "Production webhook for context events",
"secret": "your-webhook-secret-for-signature-verification"
}
# Response
{
"webhook_id": "wh_1234567890abcdef",
"url": "https://your-app.com/webhooks/bbmcp",
"events": ["context.created", "context.confirmed", "context.failed"],
"secret": "whsec_...",
"status": "active",
"created_at": "2024-01-15T10:30:00Z"
}
SDK Configuration
# Python SDK
import bb_mcp
client = bb_mcp.Client(api_key="your_api_key")
# Create webhook
webhook = client.webhooks.create(
url="https://your-app.com/webhooks/bbmcp",
events=["context.created", "context.confirmed"],
description="Context events webhook",
secret="your-webhook-secret"
)
print(f"Webhook created: {webhook.webhook_id}")
# List existing webhooks
webhooks = client.webhooks.list()
for wh in webhooks:
print(f"{wh.webhook_id}: {wh.url} ({wh.status})")
# Update webhook
client.webhooks.update(
webhook_id=webhook.webhook_id,
events=["context.created", "context.confirmed", "verification.completed"]
)
Webhook Events
Detailed payloads for each webhook event type
Context Created
Fired when a new context is successfully logged to BB-MCP (before blockchain confirmation).
{
"event": "context.created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"context_id": "ctx_1234567890abcdef",
"agent_id": "my-classification-agent",
"transaction_hash": "0x1a2b3c4d5e6f7890...",
"context_hash": "0xabcdef1234567890...",
"signature": "0x789abc...",
"status": "pending",
"created_at": "2024-01-15T10:30:00Z",
"estimated_confirmation": "2024-01-15T10:30:30Z",
"context_data": {
"task": "classify_image",
"model": "resnet50",
"confidence": 0.95
},
"metadata": {
"dataset": "imagenet-mini",
"version": "1.0"
}
}
}
Context Confirmed
Fired when a context reaches the required number of blockchain confirmations.
{
"event": "context.confirmed",
"timestamp": "2024-01-15T10:31:45Z",
"data": {
"context_id": "ctx_1234567890abcdef",
"agent_id": "my-classification-agent",
"transaction_hash": "0x1a2b3c4d5e6f7890...",
"context_hash": "0xabcdef1234567890...",
"status": "confirmed",
"block_number": 123456789,
"block_timestamp": "2024-01-15T10:30:05Z",
"confirmations": 12,
"network": "polygon",
"gas_used": 42000,
"confirmation_time_seconds": 105
}
}
Context Failed
Fired when context creation or blockchain logging fails.
{
"event": "context.failed",
"timestamp": "2024-01-15T10:30:30Z",
"data": {
"context_id": "ctx_1234567890abcdef",
"agent_id": "my-classification-agent",
"transaction_hash": null,
"status": "failed",
"error": {
"code": "BLOCKCHAIN_ERROR",
"message": "Transaction failed due to insufficient gas",
"details": "Gas limit exceeded during contract execution"
},
"retry_possible": true,
"failure_reason": "insufficient_gas"
}
}
Verification Completed
Fired when a verification request (via API) has been processed.
{
"event": "verification.completed",
"timestamp": "2024-01-15T11:45:23Z",
"data": {
"verification_id": "ver_abcdef1234567890",
"transaction_hash": "0x1a2b3c4d5e6f7890...",
"verified": true,
"agent_id": "my-classification-agent",
"context_hash": "0xabcdef1234567890...",
"signature_valid": true,
"hash_matches": true,
"block_number": 123456789,
"confirmations": 15,
"verification_time_ms": 1250
}
}
Handling Webhook Requests
Implement webhook endpoints in your application
Node.js/Express Example
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Webhook secret for signature verification
const WEBHOOK_SECRET = process.env.BBMCP_WEBHOOK_SECRET;
// Verify webhook signature
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// Webhook endpoint
app.post('/webhooks/bbmcp', (req, res) => {
const signature = req.headers['x-bbmcp-signature'];
const payload = JSON.stringify(req.body);
// Verify signature
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = req.body;
try {
switch (event.event) {
case 'context.created':
handleContextCreated(event.data);
break;
case 'context.confirmed':
handleContextConfirmed(event.data);
break;
case 'context.failed':
handleContextFailed(event.data);
break;
case 'verification.completed':
handleVerificationCompleted(event.data);
break;
default:
console.log(`Unhandled event type: ${event.event}`);
}
// Always respond with 200 to acknowledge receipt
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});
function handleContextCreated(data) {
console.log(`New context created: ${data.context_id}`);
// Example: Update database
updateContextStatus(data.context_id, 'pending');
// Example: Notify relevant systems
notifySystemsOfNewContext(data);
}
function handleContextConfirmed(data) {
console.log(`Context confirmed: ${data.context_id} at block ${data.block_number}`);
// Example: Update database with confirmation
updateContextStatus(data.context_id, 'confirmed', {
block_number: data.block_number,
confirmations: data.confirmations
});
// Example: Trigger downstream processes
processConfirmedContext(data);
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
Python/Flask Example
from flask import Flask, request, jsonify
import hashlib
import hmac
import os
import json
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('BBMCP_WEBHOOK_SECRET')
def verify_webhook_signature(payload, signature, secret):
"""Verify webhook signature using HMAC-SHA256"""
expected_signature = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@app.route('/webhooks/bbmcp', methods=['POST'])
def handle_webhook():
# Get signature from headers
signature = request.headers.get('X-BBMCP-Signature')
if not signature:
return jsonify({'error': 'Missing signature'}), 401
# Verify signature
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
event = request.json
try:
event_type = event['event']
event_data = event['data']
if event_type == 'context.created':
handle_context_created(event_data)
elif event_type == 'context.confirmed':
handle_context_confirmed(event_data)
elif event_type == 'context.failed':
handle_context_failed(event_data)
elif event_type == 'verification.completed':
handle_verification_completed(event_data)
else:
print(f"Unhandled event type: {event_type}")
return jsonify({'received': True}), 200
except Exception as e:
print(f"Webhook processing error: {e}")
return jsonify({'error': 'Processing failed'}), 500
def handle_context_created(data):
print(f"New context created: {data['context_id']}")
# Example: Store in database
store_context_update(data['context_id'], 'pending', data)
# Example: Send notification
send_notification(f"Context {data['context_id']} created")
def handle_context_confirmed(data):
print(f"Context confirmed: {data['context_id']} at block {data['block_number']}")
# Example: Update database
store_context_update(data['context_id'], 'confirmed', data)
# Example: Trigger business logic
process_confirmed_context(data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
Webhook Security
Secure your webhook endpoints and verify request authenticity
Signature Verification
BB-MCP signs all webhook requests using HMAC-SHA256. Always verify signatures to ensure requests come from BB-MCP and haven't been tampered with.
Signature Format
X-BBMCP-Signature
Security Best Practices
✅ Do
- Always verify webhook signatures
- Use HTTPS for webhook URLs
- Implement idempotency handling
- Log all webhook events for debugging
- Return 200 status for successful processing
❌ Don't
- Skip signature verification
- Use HTTP (non-SSL) webhook URLs
- Process duplicate events without checking
- Block webhook processing for too long
- Return non-2xx status for successful receipt
Reliability & Retries
How BB-MCP ensures reliable webhook delivery
Retry Policy
Attempt | Delay | Timeout | Total Elapsed |
---|---|---|---|
1st (initial) | 0 seconds | 10 seconds | 0 seconds |
2nd | 5 seconds | 10 seconds | 5 seconds |
3rd | 15 seconds | 10 seconds | 20 seconds |
4th | 45 seconds | 10 seconds | 1 min 5 sec |
5th (final) | 2 minutes | 10 seconds | 3 min 5 sec |
Handling Failures
# Check webhook delivery status
webhooks = client.webhooks.list()
for webhook in webhooks:
if webhook.status == 'failing':
print(f"Webhook {webhook.webhook_id} is failing")
# Get recent delivery attempts
deliveries = client.webhooks.get_deliveries(webhook.webhook_id, limit=10)
for delivery in deliveries:
if delivery.status == 'failed':
print(f"Failed delivery {delivery.delivery_id}:")
print(f" Status Code: {delivery.response_status}")
print(f" Error: {delivery.error_message}")
print(f" Attempts: {delivery.attempts}")
# Manually retry if needed
if delivery.attempts < 5:
client.webhooks.retry_delivery(delivery.delivery_id)
# Update webhook if endpoint changed
client.webhooks.update(
webhook_id=webhook.webhook_id,
url="https://new-endpoint.com/webhooks/bbmcp"
)
Idempotency Handling
# Python example with Redis for idempotency
import redis
redis_client = redis.Redis(host='localhost', port=6379, db=0)
@app.route('/webhooks/bbmcp', methods=['POST'])
def handle_webhook():
event = request.json
# Use event timestamp + context_id as idempotency key
idempotency_key = f"bbmcp:{event['event']}:{event['data'].get('context_id', event['timestamp'])}"
# Check if we've already processed this event
if redis_client.exists(idempotency_key):
return jsonify({'received': True, 'status': 'duplicate'}), 200
# Set idempotency key with 24 hour expiration
redis_client.setex(idempotency_key, 86400, 'processed')
# Process the event
process_webhook_event(event)
return jsonify({'received': True, 'status': 'processed'}), 200
Testing Webhooks
Tools and techniques for testing your webhook implementations
Local Development with ngrok
# Install ngrok
brew install ngrok # macOS
# or download from https://ngrok.com/
# Start your local webhook server
node webhook-server.js # Running on localhost:3000
# In another terminal, expose your local server
ngrok http 3000
# ngrok will provide a public URL like:
# https://abc123.ngrok.io
# Use this URL to create webhook in BB-MCP
curl -X POST https://api.quietstack.com/v1/webhooks \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://abc123.ngrok.io/webhooks/bbmcp",
"events": ["context.created", "context.confirmed"]
}'
Webhook Testing Service
# Use webhook.site for quick testing
# 1. Go to https://webhook.site/
# 2. Copy your unique URL
# 3. Create webhook with that URL
# Test webhook with BB-MCP CLI
bbmcp webhooks create \
--url "https://webhook.site/your-unique-id" \
--events "context.created,context.confirmed"
# Send test event
bbmcp webhooks test your_webhook_id --event context.created
# View the request details on webhook.site
Unit Testing Webhook Handlers
# Python unittest example
import unittest
import json
from unittest.mock import patch
from your_webhook_handler import app
class TestWebhookHandler(unittest.TestCase):
def setUp(self):
self.app = app.test_client()
self.webhook_secret = 'test-secret'
def create_webhook_request(self, event_data, secret):
payload = json.dumps(event_data)
signature = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return {
'data': payload,
'headers': {'X-BBMCP-Signature': signature}
}
def test_context_created_webhook(self):
event_data = {
'event': 'context.created',
'timestamp': '2024-01-15T10:30:00Z',
'data': {
'context_id': 'ctx_test123',
'agent_id': 'test-agent',
'status': 'pending'
}
}
request = self.create_webhook_request(event_data, self.webhook_secret)
with patch('your_webhook_handler.handle_context_created') as mock_handler:
response = self.app.post('/webhooks/bbmcp',
data=request['data'],
headers=request['headers'])
self.assertEqual(response.status_code, 200)
mock_handler.assert_called_once()
def test_invalid_signature(self):
event_data = {'event': 'test'}
request = self.create_webhook_request(event_data, 'wrong-secret')
response = self.app.post('/webhooks/bbmcp',
data=request['data'],
headers=request['headers'])
self.assertEqual(response.status_code, 401)
if __name__ == '__main__':
unittest.main()
Webhook Management
Dashboard Features
API Management
GET /webhooks
- List webhooksPOST /webhooks
- Create webhookPUT /webhooks/:id
- Update webhookDELETE /webhooks/:id
- Delete webhook