Delete Webhook

The Delete Webhook endpoint permanently removes a webhook configuration from your application. This operation cannot be undone and will stop all event deliveries to the specified webhook endpoint. The system ensures only authorized applications can delete their own webhooks.

Endpoint DetailsCopied!

  • URL: /api/v0/webhooks/{id}

  • Method: DELETE

  • Authentication: Required (API Key Authentication with Scopes)

  • Content-Type: application/json

  • Required Scope: webhook:delete

AuthenticationCopied!

This endpoint requires API key authentication with specific scopes:

Required Headers

x-client-key: your-client-key
x-client-secret: your-client-secret

Required Scope

Your API key must have the webhook:delete

scope to access this endpoint.

Path ParametersCopied!

Required Parameters

Parameter

Type

Description

Validation

id

string

Unique webhook identifier to delete

Must be a valid webhook ID (typically prefixed with wh_)

Request ExamplesCopied!

Basic Request

curl -X DELETE https://api.devdraft.com/api/v0/webhooks/wh_123456789 \
  -H "x-client-key: your-client-key" \
  -H "x-client-secret: your-client-secret"

JavaScript/Fetch

const webhookId = "wh_123456789";

const response = await fetch(`/api/v0/webhooks/${webhookId}`, {
  method: 'DELETE',
  headers: {
    'x-client-key': 'your-client-key',
    'x-client-secret': 'your-client-secret'
  }
});

const deletedWebhook = await response.json();

cURL with Verbose Output

curl -X DELETE https://api.devdraft.com/api/v0/webhooks/wh_123456789 \
  -H "x-client-key: your-client-key" \
  -H "x-client-secret: your-client-secret" \
  -v

ResponseCopied!

Success Response (200 OK)

Returns the deleted webhook object for confirmation:

{
  "id": "wh_123456789",
  "name": "Payment Notifications",
  "url": "https://api.example.com/webhooks/payments",
  "isActive": true,
  "encrypted": false,
  "created_at": "2024-03-20T12:00:00.000Z",
  "updated_at": "2024-03-20T12:00:00.000Z",
  "delivery_stats": {
    "total_events": 150,
    "successful_deliveries": 145,
    "failed_deliveries": 5,
    "last_delivery": "2024-03-20T11:55:00.000Z"
  }
}

Error Responses

401 Unauthorized - Missing Credentials
{
  "statusCode": 401,
  "message": "Client key or secret missing",
  "error": "Unauthorized",
  "details": "Please provide both x-client-key and x-client-secret headers"
}
401 Unauthorized - Invalid Credentials
{
  "statusCode": 401,
  "message": "Invalid client app credentials",
  "error": "Unauthorized",
  "details": "The provided API key or secret is invalid"
}
403 Forbidden - Missing Scope
{
  "statusCode": 403,
  "message": "Missing required scope",
  "error": "Forbidden",
  "details": "API key does not have the required webhook:delete scope"
}
403 Forbidden - Inactive API Key
{
  "statusCode": 403,
  "message": "API key is inactive",
  "error": "Forbidden",
  "details": "The provided API key has been deactivated"
}
404 Not Found
{
  "statusCode": 404,
  "message": "Webhook not found",
  "error": "Not Found"
}
400 Bad Request - Invalid ID Format
{
  "statusCode": 400,
  "message": "Invalid webhook ID format",
  "error": "Bad Request"
}

Business LogicCopied!

Access Control

  • Only webhooks belonging to your authenticated application can be deleted

  • The system verifies ownership before allowing deletion

  • Cross-application webhook deletion is strictly prohibited

Permanent Deletion

  • This operation permanently removes the webhook from the database

  • All webhook delivery history is retained for audit purposes

  • The webhook cannot be recovered after deletion

  • Event deliveries to this webhook will immediately stop

Data Retention

  • Webhook delivery statistics may be preserved for reporting purposes

  • Event logs associated with this webhook remain accessible

  • Audit trails of webhook creation and deletion are maintained

Use CasesCopied!

1. Cleanup Unused Webhooks

Remove webhooks that are no longer needed:

async function cleanupUnusedWebhook(webhookId, reason) {
  try {
    // First, verify the webhook exists and get its details
    const webhook = await fetchWebhook(webhookId);
    
    if (!webhook) {
      throw new Error('Webhook not found');
    }
    
    // Log the deletion for audit purposes
    console.log(`Deleting webhook: ${webhook.name} (${webhook.id})`);
    console.log(`Reason: ${reason}`);
    console.log(`Total events processed: ${webhook.delivery_stats.total_events}`);
    
    // Perform the deletion
    const deletedWebhook = await deleteWebhook(webhookId);
    
    console.log(`Successfully deleted webhook: ${deletedWebhook.name}`);
    
    return {
      success: true,
      deletedWebhook,
      metadata: {
        deletedAt: new Date().toISOString(),
        reason,
        finalStats: deletedWebhook.delivery_stats
      }
    };
  } catch (error) {
    console.error(`Failed to delete webhook ${webhookId}:`, error);
    throw error;
  }
}

2. Migrate Webhook Endpoints

Replace old webhooks with new configurations:

async function migrateWebhook(oldWebhookId, newWebhookConfig) {
  try {
    // Get the old webhook details for reference
    const oldWebhook = await fetchWebhook(oldWebhookId);
    
    if (!oldWebhook) {
      throw new Error('Old webhook not found');
    }
    
    // Create the new webhook first
    const newWebhook = await createWebhook({
      name: newWebhookConfig.name || `${oldWebhook.name} (Migrated)`,
      url: newWebhookConfig.url,
      isActive: newWebhookConfig.isActive !== false,
      encrypted: newWebhookConfig.encrypted || oldWebhook.encrypted
    });
    
    console.log(`Created new webhook: ${newWebhook.id}`);
    
    // Test the new webhook (optional)
    if (newWebhookConfig.testBeforeSwitch) {
      await testWebhookEndpoint(newWebhook.url);
      console.log('New webhook endpoint test passed');
    }
    
    // Delete the old webhook
    const deletedWebhook = await deleteWebhook(oldWebhookId);
    
    return {
      migration: 'success',
      oldWebhook: {
        id: deletedWebhook.id,
        name: deletedWebhook.name,
        finalStats: deletedWebhook.delivery_stats
      },
      newWebhook: {
        id: newWebhook.id,
        name: newWebhook.name,
        url: newWebhook.url
      },
      migratedAt: new Date().toISOString()
    };
  } catch (error) {
    console.error('Webhook migration failed:', error);
    throw error;
  }
}

3. Automated Cleanup Based on Health

Remove consistently failing webhooks:

async function cleanupUnhealthyWebhooks(failureThreshold = 0.5, minEvents = 100) {
  try {
    const allWebhooks = await fetchAllWebhooks();
    const candidatesForDeletion = [];
    
    for (const webhook of allWebhooks) {
      const stats = webhook.delivery_stats;
      
      // Only consider webhooks with significant activity
      if (stats.total_events >= minEvents) {
        const failureRate = stats.failed_deliveries / stats.total_events;
        
        if (failureRate >= failureThreshold) {
          candidatesForDeletion.push({
            webhook,
            failureRate,
            reasonForDeletion: `High failure rate: ${(failureRate * 100).toFixed(1)}%`
          });
        }
      }
    }
    
    console.log(`Found ${candidatesForDeletion.length} webhooks for cleanup`);
    
    const deletionResults = [];
    
    for (const candidate of candidatesForDeletion) {
      try {
        // Optional: Send notification before deletion
        await notifyWebhookOwner(candidate.webhook, candidate.reasonForDeletion);
        
        // Delete the webhook
        const deletedWebhook = await deleteWebhook(candidate.webhook.id);
        
        deletionResults.push({
          success: true,
          webhook: deletedWebhook,
          reason: candidate.reasonForDeletion
        });
        
        console.log(`Deleted unhealthy webhook: ${deletedWebhook.name}`);
      } catch (error) {
        deletionResults.push({
          success: false,
          webhookId: candidate.webhook.id,
          error: error.message
        });
      }
    }
    
    return {
      candidatesFound: candidatesForDeletion.length,
      successfulDeletions: deletionResults.filter(r => r.success).length,
      failedDeletions: deletionResults.filter(r => !r.success).length,
      results: deletionResults
    };
  } catch (error) {
    console.error('Cleanup process failed:', error);
    throw error;
  }
}

4. Safe Deletion with Backup

Delete webhooks while preserving important information:

async function safeDeleteWebhook(webhookId, options = {}) {
  try {
    // Get webhook details for backup
    const webhook = await fetchWebhook(webhookId);
    
    if (!webhook) {
      throw new Error('Webhook not found');
    }
    
    // Create backup record
    const backup = {
      originalWebhook: webhook,
      deletedAt: new Date().toISOString(),
      deletedBy: options.deletedBy || 'system',
      reason: options.reason || 'manual deletion',
      canRestore: options.canRestore !== false
    };
    
    // Store backup if backup storage is available
    if (options.backupStorage) {
      await options.backupStorage.store(`webhook_backup_${webhookId}`, backup);
      console.log(`Backup created for webhook: ${webhookId}`);
    }
    
    // Optionally deactivate before deletion to stop events gracefully
    if (options.deactivateFirst && webhook.isActive) {
      await updateWebhook(webhookId, { isActive: false });
      console.log(`Deactivated webhook before deletion: ${webhookId}`);
      
      // Wait for any in-flight events to complete
      if (options.gracePeriodMs) {
        await new Promise(resolve => setTimeout(resolve, options.gracePeriodMs));
      }
    }
    
    // Perform the deletion
    const deletedWebhook = await deleteWebhook(webhookId);
    
    return {
      success: true,
      deletedWebhook,
      backup: options.backupStorage ? backup : null,
      metadata: {
        deletionTime: new Date().toISOString(),
        finalStats: deletedWebhook.delivery_stats,
        gracefulShutdown: !!options.deactivateFirst
      }
    };
  } catch (error) {
    console.error(`Safe deletion failed for webhook ${webhookId}:`, error);
    throw error;
  }
}

Integration ExamplesCopied!

React Component with Confirmation

import React, { useState } from 'react';

function WebhookDeleteButton({ webhook, onDeleted, onError }) {
  const [isDeleting, setIsDeleting] = useState(false);
  const [showConfirmation, setShowConfirmation] = useState(false);

  const handleDelete = async () => {
    setIsDeleting(true);
    
    try {
      const response = await fetch(`/api/v0/webhooks/${webhook.id}`, {
        method: 'DELETE',
        headers: {
          'x-client-key': process.env.REACT_APP_CLIENT_KEY,
          'x-client-secret': process.env.REACT_APP_CLIENT_SECRET
        }
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message || 'Failed to delete webhook');
      }

      const deletedWebhook = await response.json();
      
      // Close confirmation dialog
      setShowConfirmation(false);
      
      // Notify parent component
      onDeleted(deletedWebhook);
      
    } catch (error) {
      onError(error.message);
    } finally {
      setIsDeleting(false);
    }
  };

  const getWarningMessage = () => {
    const stats = webhook.delivery_stats;
    const messages = [];
    
    if (webhook.isActive) {
      messages.push('This webhook is currently active and receiving events.');
    }
    
    if (stats.total_events > 0) {
      messages.push(`This webhook has processed ${stats.total_events} events.`);
    }
    
    messages.push('This action cannot be undone.');
    
    return messages;
  };

  if (showConfirmation) {
    return (
      <div className="delete-confirmation">
        <div className="confirmation-header">
          <h3>Delete Webhook: "{webhook.name}"</h3>
        </div>
        
        <div className="confirmation-content">
          <div className="webhook-info">
            <p><strong>ID:</strong> {webhook.id}</p>
            <p><strong>URL:</strong> {webhook.url}</p>
            <p><strong>Status:</strong> {webhook.isActive ? 'Active' : 'Inactive'}</p>
          </div>
          
          <div className="warning-messages">
            {getWarningMessage().map((message, index) => (
              <p key={index} className="warning-text">⚠️ {message}</p>
            ))}
          </div>
        </div>
        
        <div className="confirmation-actions">
          <button 
            onClick={handleDelete} 
            disabled={isDeleting}
            className="btn-danger"
          >
            {isDeleting ? 'Deleting...' : 'Yes, Delete Webhook'}
          </button>
          <button 
            onClick={() => setShowConfirmation(false)}
            disabled={isDeleting}
            className="btn-secondary"
          >
            Cancel
          </button>
        </div>
      </div>
    );
  }

  return (
    <button 
      onClick={() => setShowConfirmation(true)}
      className="btn-danger"
      title="Delete webhook permanently"
    >
      Delete
    </button>
  );
}

// Usage in a webhook management component
function WebhookManagement() {
  const [webhooks, setWebhooks] = useState([]);
  const [message, setMessage] = useState(null);

  const handleWebhookDeleted = (deletedWebhook) => {
    setWebhooks(prev => prev.filter(w => w.id !== deletedWebhook.id));
    setMessage({
      type: 'success',
      text: `Webhook "${deletedWebhook.name}" has been deleted successfully.`
    });
    
    // Clear message after 5 seconds
    setTimeout(() => setMessage(null), 5000);
  };

  const handleDeleteError = (error) => {
    setMessage({
      type: 'error',
      text: `Failed to delete webhook: ${error}`
    });
    
    // Clear message after 5 seconds
    setTimeout(() => setMessage(null), 5000);
  };

  return (
    <div className="webhook-management">
      {message && (
        <div className={`message ${message.type}`}>
          {message.text}
        </div>
      )}
      
      <div className="webhook-list">
        {webhooks.map(webhook => (
          <div key={webhook.id} className="webhook-item">
            <div className="webhook-info">
              <h3>{webhook.name}</h3>
              <p>{webhook.url}</p>
            </div>
            
            <div className="webhook-actions">
              <WebhookDeleteButton
                webhook={webhook}
                onDeleted={handleWebhookDeleted}
                onError={handleDeleteError}
              />
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Python Service

import requests
from typing import Dict, Any, Optional, List
import logging

class WebhookDeletionService:
    def __init__(self, client_key: str, client_secret: str, base_url: str):
        self.client_key = client_key
        self.client_secret = client_secret
        self.base_url = base_url
        self.logger = logging.getLogger(__name__)
        
    def _get_headers(self) -> Dict[str, str]:
        return {
            'x-client-key': self.client_key,
            'x-client-secret': self.client_secret
        }
    
    def delete_webhook(self, webhook_id: str) -> Dict[str, Any]:
        """Delete a webhook by ID"""
        try:
            response = requests.delete(
                f"{self.base_url}/api/v0/webhooks/{webhook_id}",
                headers=self._get_headers()
            )
            
            if response.status_code == 200:
                return response.json()
            elif response.status_code == 404:
                raise ValueError(f"Webhook {webhook_id} not found")
            elif response.status_code == 403:
                raise PermissionError("Insufficient permissions to delete webhook")
            else:
                response.raise_for_status()
                
        except requests.exceptions.RequestException as e:
            self.logger.error(f"Error deleting webhook {webhook_id}: {e}")
            raise
    
    def safe_delete_webhook(self, webhook_id: str, backup_callback=None) -> Dict[str, Any]:
        """Safely delete a webhook with optional backup"""
        try:
            # First, get webhook details for backup
            webhook_response = requests.get(
                f"{self.base_url}/api/v0/webhooks/{webhook_id}",
                headers=self._get_headers()
            )
            
            if webhook_response.status_code != 200:
                raise ValueError("Webhook not found or access denied")
                
            webhook = webhook_response.json()
            
            # Create backup if callback provided
            if backup_callback:
                backup_data = {
                    'webhook': webhook,
                    'deleted_at': requests.packages.urllib3.util.connection.datetime.now().isoformat(),
                    'deletion_method': 'safe_delete'
                }
                backup_callback(webhook_id, backup_data)
                self.logger.info(f"Backup created for webhook {webhook_id}")
            
            # Perform deletion
            deleted_webhook = self.delete_webhook(webhook_id)
            
            self.logger.info(f"Successfully deleted webhook: {deleted_webhook['name']} ({webhook_id})")
            
            return {
                'success': True,
                'deleted_webhook': deleted_webhook,
                'backup_created': backup_callback is not None,
                'final_stats': deleted_webhook['delivery_stats']
            }
            
        except Exception as e:
            self.logger.error(f"Safe deletion failed for webhook {webhook_id}: {e}")
            raise
    
    def batch_delete_webhooks(self, webhook_ids: List[str], 
                            confirm_callback=None) -> Dict[str, List]:
        """Delete multiple webhooks with optional confirmation"""
        results = {
            'successful': [],
            'failed': [],
            'skipped': []
        }
        
        for webhook_id in webhook_ids:
            try:
                # Optional confirmation callback
                if confirm_callback and not confirm_callback(webhook_id):
                    results['skipped'].append({
                        'webhook_id': webhook_id,
                        'reason': 'User confirmation declined'
                    })
                    continue
                
                deleted_webhook = self.delete_webhook(webhook_id)
                results['successful'].append({
                    'webhook_id': webhook_id,
                    'name': deleted_webhook['name'],
                    'final_stats': deleted_webhook['delivery_stats']
                })
                
            except Exception as e:
                results['failed'].append({
                    'webhook_id': webhook_id,
                    'error': str(e)
                })
        
        self.logger.info(
            f"Batch deletion complete: {len(results['successful'])} successful, "
            f"{len(results['failed'])} failed, {len(results['skipped'])} skipped"
        )
        
        return results
    
    def cleanup_inactive_webhooks(self, days_inactive: int = 30) -> Dict[str, Any]:
        """Delete webhooks that have been inactive for specified days"""
        try:
            # Get all webhooks
            response = requests.get(
                f"{self.base_url}/api/v0/webhooks",
                headers=self._get_headers()
            )
            response.raise_for_status()
            webhooks = response.json()
            
            # Find inactive webhooks
            from datetime import datetime, timedelta
            cutoff_date = datetime.now() - timedelta(days=days_inactive)
            
            inactive_webhooks = []
            for webhook in webhooks:
                if not webhook['isActive']:
                    continue
                
                last_delivery = webhook['delivery_stats']['last_delivery']
                if not last_delivery:
                    # Never delivered, check creation date
                    created_at = datetime.fromisoformat(webhook['created_at'].replace('Z', '+00:00'))
                    if created_at < cutoff_date:
                        inactive_webhooks.append(webhook)
                else:
                    last_delivery_date = datetime.fromisoformat(last_delivery.replace('Z', '+00:00'))
                    if last_delivery_date < cutoff_date:
                        inactive_webhooks.append(webhook)
            
            # Delete inactive webhooks
            deletion_results = []
            for webhook in inactive_webhooks:
                try:
                    deleted = self.delete_webhook(webhook['id'])
                    deletion_results.append({
                        'success': True,
                        'webhook': deleted,
                        'reason': f'Inactive for {days_inactive}+ days'
                    })
                except Exception as e:
                    deletion_results.append({
                        'success': False,
                        'webhook_id': webhook['id'],
                        'error': str(e)
                    })
            
            return {
                'total_webhooks': len(webhooks),
                'inactive_found': len(inactive_webhooks),
                'deleted_successfully': len([r for r in deletion_results if r['success']]),
                'deletion_failures': len([r for r in deletion_results if not r['success']]),
                'results': deletion_results
            }
            
        except Exception as e:
            self.logger.error(f"Cleanup process failed: {e}")
            raise

# Usage examples
def backup_webhook(webhook_id: str, backup_data: Dict[str, Any]):
    """Example backup callback"""
    import json
    with open(f"webhook_backup_{webhook_id}.json", 'w') as f:
        json.dump(backup_data, f, indent=2)
    print(f"Backup saved for webhook {webhook_id}")

def confirm_deletion(webhook_id: str) -> bool:
    """Example confirmation callback"""
    response = input(f"Delete webhook {webhook_id}? (y/N): ")
    return response.lower() == 'y'

# Initialize service
deletion_service = WebhookDeletionService(
    'your-key', 
    'your-secret', 
    'https://api.devdraft.com'
)

# Safe delete with backup
result = deletion_service.safe_delete_webhook(
    'wh_123456789',
    backup_callback=backup_webhook
)

# Batch delete with confirmation
webhook_ids = ['wh_111', 'wh_222', 'wh_333']
batch_results = deletion_service.batch_delete_webhooks(
    webhook_ids,
    confirm_callback=confirm_deletion
)

# Cleanup inactive webhooks
cleanup_results = deletion_service.cleanup_inactive_webhooks(days_inactive=30)
print(f"Cleaned up {cleanup_results['deleted_successfully']} inactive webhooks")

Node.js/Express Service

const axios = require('axios');
const fs = require('fs').promises;

class WebhookDeletionManager {
  constructor(clientKey, clientSecret, baseUrl) {
    this.clientKey = clientKey;
    this.clientSecret = clientSecret;
    this.baseUrl = baseUrl;
  }

  async deleteWebhook(webhookId) {
    try {
      const response = await axios.delete(
        `${this.baseUrl}/api/v0/webhooks/${webhookId}`,
        {
          headers: {
            'x-client-key': this.clientKey,
            'x-client-secret': this.clientSecret
          }
        }
      );

      return response.data;
    } catch (error) {
      if (error.response) {
        const { status, data } = error.response;
        
        switch (status) {
          case 404:
            throw new Error('Webhook not found');
          case 403:
            throw new Error('Insufficient permissions to delete webhook');
          case 401:
            throw new Error('Invalid API credentials');
          default:
            throw new Error(data.message || 'Failed to delete webhook');
        }
      }
      throw error;
    }
  }

  async safeDeleteWithBackup(webhookId, options = {}) {
    try {
      // Get webhook details first
      const webhook = await this.getWebhook(webhookId);
      
      // Create backup
      const backup = {
        webhook,
        deletedAt: new Date().toISOString(),
        deletedBy: options.deletedBy || 'api',
        reason: options.reason || 'manual deletion'
      };

      // Save backup if requested
      if (options.saveBackup) {
        const backupPath = `./backups/webhook_${webhookId}_${Date.now()}.json`;
        await fs.writeFile(backupPath, JSON.stringify(backup, null, 2));
        console.log(`Backup saved to: ${backupPath}`);
      }

      // Deactivate first if requested (graceful shutdown)
      if (options.deactivateFirst && webhook.isActive) {
        await this.updateWebhook(webhookId, { isActive: false });
        console.log(`Deactivated webhook ${webhookId} before deletion`);
        
        // Wait for grace period
        if (options.gracePeriodMs) {
          await new Promise(resolve => setTimeout(resolve, options.gracePeriodMs));
        }
      }

      // Perform deletion
      const deletedWebhook = await this.deleteWebhook(webhookId);
      
      return {
        success: true,
        deletedWebhook,
        backup: options.saveBackup ? backup : null,
        gracefulShutdown: !!options.deactivateFirst
      };
    } catch (error) {
      console.error(`Safe deletion failed for webhook ${webhookId}:`, error);
      throw error;
    }
  }

  async getWebhook(webhookId) {
    const response = await axios.get(
      `${this.baseUrl}/api/v0/webhooks/${webhookId}`,
      {
        headers: {
          'x-client-key': this.clientKey,
          'x-client-secret': this.clientSecret
        }
      }
    );
    return response.data;
  }

  async updateWebhook(webhookId, updates) {
    const response = await axios.patch(
      `${this.baseUrl}/api/v0/webhooks/${webhookId}`,
      updates,
      {
        headers: {
          'Content-Type': 'application/json',
          'x-client-key': this.clientKey,
          'x-client-secret': this.clientSecret
        }
      }
    );
    return response.data;
  }

  async batchDeleteWithConfirmation(webhookIds, confirmationCallback) {
    const results = {
      successful: [],
      failed: [],
      skipped: []
    };

    for (const webhookId of webhookIds) {
      try {
        // Get webhook details for confirmation
        const webhook = await this.getWebhook(webhookId);
        
        // Ask for confirmation if callback provided
        if (confirmationCallback) {
          const confirmed = await confirmationCallback(webhook);
          if (!confirmed) {
            results.skipped.push({
              webhookId,
              name: webhook.name,
              reason: 'User declined confirmation'
            });
            continue;
          }
        }

        // Delete the webhook
        const deletedWebhook = await this.deleteWebhook(webhookId);
        
        results.successful.push({
          webhookId,
          name: deletedWebhook.name,
          finalStats: deletedWebhook.delivery_stats
        });
        
        console.log(`Successfully deleted webhook: ${deletedWebhook.name}`);
        
      } catch (error) {
        results.failed.push({
          webhookId,
          error: error.message
        });
        
        console.error(`Failed to delete webhook ${webhookId}:`, error.message);
      }
    }

    return results;
  }

  async cleanupFailedWebhooks(failureThreshold = 0.3, minEvents = 50) {
    try {
      // Get all webhooks
      const response = await axios.get(
        `${this.baseUrl}/api/v0/webhooks`,
        {
          headers: {
            'x-client-key': this.clientKey,
            'x-client-secret': this.clientSecret
          }
        }
      );
      
      const webhooks = response.data;
      
      // Identify failed webhooks
      const failedWebhooks = webhooks.filter(webhook => {
        const stats = webhook.delivery_stats;
        if (stats.total_events < minEvents) return false;
        
        const failureRate = stats.failed_deliveries / stats.total_events;
        return failureRate >= failureThreshold;
      });

      console.log(`Found ${failedWebhooks.length} webhooks exceeding failure threshold`);

      // Delete failed webhooks
      const deletionResults = [];
      
      for (const webhook of failedWebhooks) {
        try {
          const deletedWebhook = await this.deleteWebhook(webhook.id);
          deletionResults.push({
            success: true,
            webhook: deletedWebhook,
            failureRate: (webhook.delivery_stats.failed_deliveries / webhook.delivery_stats.total_events * 100).toFixed(1)
          });
        } catch (error) {
          deletionResults.push({
            success: false,
            webhookId: webhook.id,
            error: error.message
          });
        }
      }

      return {
        totalWebhooks: webhooks.length,
        failedWebhooksFound: failedWebhooks.length,
        successfulDeletions: deletionResults.filter(r => r.success).length,
        failedDeletions: deletionResults.filter(r => !r.success).length,
        results: deletionResults
      };
      
    } catch (error) {
      console.error('Cleanup process failed:', error);
      throw error;
    }
  }
}

// Express endpoints
const express = require('express');
const app = express();

const deletionManager = new WebhookDeletionManager(
  process.env.CLIENT_KEY,
  process.env.CLIENT_SECRET,
  'https://api.devdraft.com'
);

// Delete webhook endpoint with backup
app.delete('/webhooks/:id', async (req, res) => {
  try {
    const result = await deletionManager.safeDeleteWithBackup(req.params.id, {
      saveBackup: req.query.backup === 'true',
      deactivateFirst: req.query.graceful === 'true',
      gracePeriodMs: parseInt(req.query.gracePeriod) || 5000,
      deletedBy: req.headers['x-user-id'] || 'api',
      reason: req.query.reason || 'manual deletion'
    });
    
    res.json({
      message: 'Webhook deleted successfully',
      ...result
    });
  } catch (error) {
    console.error('Webhook deletion error:', error);
    res.status(400).json({ error: error.message });
  }
});

// Batch delete endpoint
app.post('/webhooks/batch-delete', async (req, res) => {
  try {
    const { webhookIds, requireConfirmation } = req.body;
    
    const confirmationCallback = requireConfirmation 
      ? async (webhook) => {
          // In a real app, this might send a notification and wait for response
          console.log(`Confirmation required for webhook: ${webhook.name}`);
          return true; // Auto-confirm for demo
        }
      : null;
    
    const results = await deletionManager.batchDeleteWithConfirmation(
      webhookIds,
      confirmationCallback
    );
    
    res.json(results);
  } catch (error) {
    console.error('Batch deletion error:', error);
    res.status(500).json({ error: error.message });
  }
});

// Cleanup endpoint
app.post('/webhooks/cleanup', async (req, res) => {
  try {
    const { 
      failureThreshold = 0.3, 
      minEvents = 50 
    } = req.body;
    
    const results = await deletionManager.cleanupFailedWebhooks(
      failureThreshold,
      minEvents
    );
    
    res.json(results);
  } catch (error) {
    console.error('Cleanup error:', error);
    res.status(500).json({ error: error.message });
  }
});

module.exports = { WebhookDeletionManager, app };

Safety ConsiderationsCopied!

1. Pre-Deletion Validation

Always verify webhook details before deletion:

async function validateBeforeDeletion(webhookId) {
  const webhook = await fetchWebhook(webhookId);
  
  const validationResults = {
    canDelete: true,
    warnings: [],
    blockers: []
  };
  
  if (webhook.isActive) {
    validationResults.warnings.push('Webhook is currently active - events will stop immediately');
  }
  
  if (webhook.delivery_stats.total_events > 1000) {
    validationResults.warnings.push('Webhook has processed significant traffic');
  }
  
  // Add any business-specific validation rules
  if (webhook.name.includes('critical')) {
    validationResults.blockers.push('Cannot delete webhooks marked as critical');
    validationResults.canDelete = false;
  }
  
  return validationResults;
}

2. Graceful Shutdown

Deactivate webhooks before deletion to handle in-flight events:

async function gracefulWebhookDeletion(webhookId, gracePeriodMs = 30000) {
  const webhook = await fetchWebhook(webhookId);
  
  if (webhook.isActive) {
    // Deactivate first
    await updateWebhook(webhookId, { isActive: false });
    console.log(`Deactivated webhook ${webhookId}, waiting ${gracePeriodMs}ms`);
    
    // Wait for in-flight events to complete
    await new Promise(resolve => setTimeout(resolve, gracePeriodMs));
  }
  
  // Now safe to delete
  return await deleteWebhook(webhookId);
}

3. Backup and Recovery

Create backups before deletion for potential recovery:

async function createWebhookBackup(webhook) {
  const backup = {
    webhook,
    backup_created_at: new Date().toISOString(),
    recovery_instructions: 'Use the create webhook endpoint with this configuration'
  };
  
  // Store backup (implementation depends on your backup system)
  await storeBackup(`webhook_${webhook.id}`, backup);
  
  return backup;
}

Rate LimitingCopied!

This endpoint is subject to the standard API rate limits:

  • Production: 1000 requests per hour per API key

  • Development: 100 requests per hour per API key

Security and ComplianceCopied!

Access Control

  • Only webhooks belonging to your authenticated application can be deleted

  • API key scoping ensures proper authorization

  • Audit trails are maintained for deletion operations

Data Protection

  • Webhook configuration is permanently removed

  • Delivery statistics may be retained for reporting

  • Backup strategies should be implemented for compliance

Audit Requirements

  • Log all webhook deletions with timestamps and reasons

  • Maintain deletion audit trails for compliance

  • Consider notification systems for webhook removal

Best PracticesCopied!

1. Confirmation Workflows

Always implement confirmation for webhook deletion in user interfaces

2. Backup Strategies

Create backups of webhook configurations before deletion

3. Graceful Deactivation

Deactivate webhooks before deletion to handle in-flight events properly

4. Audit Logging

Log all deletion operations with context and reasoning