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

  1. 1Event occurs in BB-MCP (context confirmed, etc.)
  2. 2BB-MCP sends POST request to your webhook URL
  3. 3Your application processes the event
  4. 4You respond with HTTP 200 to acknowledge receipt

Available Events

context.created
New context logged
context.confirmed
Context confirmed on blockchain
context.failed
Context verification failed
verification.completed
Verification request processed

Setting Up Webhooks

Configure webhook endpoints using the API or dashboard

Using the Dashboard

1
Navigate to Webhooks
Go to your dashboard and click "Webhooks" in the sidebar
2
Add New Webhook
Click "Create Webhook" and provide URL and event types
3
Test the Webhook
Use the "Send Test Event" button to verify your endpoint

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

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

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

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

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

Header: X-BBMCP-Signature
Algorithm: HMAC-SHA256
Secret: Your webhook secret (set during creation)
Payload: Raw request body (JSON string)

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

AttemptDelayTimeoutTotal Elapsed
1st (initial)0 seconds10 seconds0 seconds
2nd5 seconds10 seconds5 seconds
3rd15 seconds10 seconds20 seconds
4th45 seconds10 seconds1 min 5 sec
5th (final)2 minutes10 seconds3 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

• View all configured webhooks
• Monitor delivery success rates
• Manually retry failed deliveries
• Test webhooks with sample events
• View delivery logs and debug info

API Management

GET /webhooks - List webhooks
POST /webhooks - Create webhook
PUT /webhooks/:id - Update webhook
DELETE /webhooks/:id - Delete webhook