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 |
---|---|---|---|
|
|
Unique webhook identifier to delete |
Must be a valid webhook ID (typically prefixed with |
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