List Webhooks
The List Webhooks endpoint retrieves all webhook configurations associated with your authenticated application. This endpoint provides comprehensive information about each webhook, including delivery statistics and current status, enabling effective webhook management and monitoring.
Endpoint DetailsCopied!
-
URL:
/api/v0/webhooks
-
Method:
GET
-
Authentication: Required (API Key Authentication with Scopes)
-
Content-Type:
application/json
-
Required Scope:
webhook:read
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:read
scope to access this endpoint.
Query ParametersCopied!
Optional Parameters
Parameter |
Type |
Description |
Default |
Validation |
---|---|---|---|---|
|
|
Number of records to skip for pagination |
|
Non-negative integer |
|
|
Number of records to return |
|
Positive integer |
Request ExamplesCopied!
Basic Request
curl -X GET https://api.devdraft.com/api/v0/webhooks \
-H "x-client-key: your-client-key" \
-H "x-client-secret: your-client-secret"
With Pagination
curl -X GET "https://api.devdraft.com/api/v0/webhooks?skip=0&take=20" \
-H "x-client-key: your-client-key" \
-H "x-client-secret: your-client-secret"
JavaScript/Fetch
const response = await fetch('/api/v0/webhooks?skip=0&take=10', {
headers: {
'x-client-key': 'your-client-key',
'x-client-secret': 'your-client-secret'
}
});
const webhooks = await response.json();
ResponseCopied!
Success Response (200 OK)
Returns an array of webhook objects with delivery statistics:
[
{
"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"
}
},
{
"id": "wh_987654321",
"name": "Order Updates",
"url": "https://api.example.com/webhooks/orders",
"isActive": true,
"encrypted": true,
"created_at": "2024-03-15T09:30:00.000Z",
"updated_at": "2024-03-18T14:20:00.000Z",
"delivery_stats": {
"total_events": 89,
"successful_deliveries": 87,
"failed_deliveries": 2,
"last_delivery": "2024-03-20T10:30:00.000Z"
}
},
{
"id": "wh_555666777",
"name": "Test Webhook",
"url": "https://webhook.site/test-endpoint",
"isActive": false,
"encrypted": false,
"created_at": "2024-03-18T16:45:00.000Z",
"updated_at": "2024-03-19T08:15:00.000Z",
"delivery_stats": {
"total_events": 0,
"successful_deliveries": 0,
"failed_deliveries": 0,
"last_delivery": null
}
}
]
Empty Response (200 OK)
[]
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:read scope"
}
403 Forbidden - Inactive API Key
{
"statusCode": 403,
"message": "API key is inactive",
"error": "Forbidden",
"details": "The provided API key has been deactivated"
}
Response FieldsCopied!
Webhook Object Fields
Field |
Type |
Description |
---|---|---|
|
|
Unique webhook identifier (prefixed with |
|
|
Human-readable webhook name |
|
|
Endpoint URL where events are sent |
|
|
Whether the webhook is currently active |
|
|
Whether webhook payloads are encrypted |
|
|
ISO 8601 timestamp of webhook creation |
|
|
ISO 8601 timestamp of last modification |
|
|
Webhook delivery performance metrics |
Delivery Statistics Object
Field |
Type |
Description |
---|---|---|
|
|
Total number of events sent to this webhook |
|
|
Number of successful deliveries (HTTP 2xx responses) |
|
|
Number of failed deliveries (non-2xx responses) |
|
|
ISO 8601 timestamp of most recent delivery attempt |
Use CasesCopied!
1. Webhook Management Dashboard
Display all webhooks with their status and performance:
async function loadWebhookDashboard() {
try {
const webhooks = await fetchWebhooks();
return webhooks.map(webhook => ({
id: webhook.id,
name: webhook.name,
status: webhook.isActive ? 'Active' : 'Inactive',
successRate: webhook.delivery_stats.total_events > 0
? (webhook.delivery_stats.successful_deliveries / webhook.delivery_stats.total_events * 100).toFixed(1)
: 'N/A',
lastDelivery: webhook.delivery_stats.last_delivery
? new Date(webhook.delivery_stats.last_delivery).toLocaleDateString()
: 'Never',
encrypted: webhook.encrypted ? 'Yes' : 'No'
}));
} catch (error) {
console.error('Failed to load webhook dashboard:', error);
throw error;
}
}
2. Health Monitoring
Monitor webhook health and identify issues:
async function getWebhookHealth() {
const webhooks = await fetchWebhooks();
return {
total: webhooks.length,
active: webhooks.filter(w => w.isActive).length,
inactive: webhooks.filter(w => !w.isActive).length,
withFailures: webhooks.filter(w => w.delivery_stats.failed_deliveries > 0).length,
healthyWebhooks: webhooks.filter(w => {
const stats = w.delivery_stats;
if (stats.total_events === 0) return true;
return (stats.successful_deliveries / stats.total_events) >= 0.95;
}).length
};
}
3. Performance Analytics
Analyze webhook performance across all endpoints:
async function getWebhookAnalytics() {
const webhooks = await fetchWebhooks();
const totalStats = webhooks.reduce((acc, webhook) => {
const stats = webhook.delivery_stats;
return {
totalEvents: acc.totalEvents + stats.total_events,
successfulDeliveries: acc.successfulDeliveries + stats.successful_deliveries,
failedDeliveries: acc.failedDeliveries + stats.failed_deliveries
};
}, { totalEvents: 0, successfulDeliveries: 0, failedDeliveries: 0 });
return {
...totalStats,
successRate: totalStats.totalEvents > 0
? (totalStats.successfulDeliveries / totalStats.totalEvents * 100).toFixed(2)
: '0',
failureRate: totalStats.totalEvents > 0
? (totalStats.failedDeliveries / totalStats.totalEvents * 100).toFixed(2)
: '0'
};
}
4. Webhook Filtering and Search
Filter webhooks based on various criteria:
async function filterWebhooks(criteria) {
const webhooks = await fetchWebhooks();
return webhooks.filter(webhook => {
if (criteria.status && webhook.isActive !== (criteria.status === 'active')) {
return false;
}
if (criteria.encrypted !== undefined && webhook.encrypted !== criteria.encrypted) {
return false;
}
if (criteria.nameSearch && !webhook.name.toLowerCase().includes(criteria.nameSearch.toLowerCase())) {
return false;
}
if (criteria.hasFailures && webhook.delivery_stats.failed_deliveries === 0) {
return false;
}
return true;
});
}
// Usage examples
const activeWebhooks = await filterWebhooks({ status: 'active' });
const encryptedWebhooks = await filterWebhooks({ encrypted: true });
const webhooksWithFailures = await filterWebhooks({ hasFailures: true });
Integration ExamplesCopied!
React Component
import React, { useState, useEffect } from 'react';
function WebhookList() {
const [webhooks, setWebhooks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [page, setPage] = useState(0);
const [pageSize] = useState(10);
useEffect(() => {
loadWebhooks();
}, [page]);
const loadWebhooks = async () => {
try {
setLoading(true);
const response = await fetch(
`/api/v0/webhooks?skip=${page * pageSize}&take=${pageSize}`,
{
headers: {
'x-client-key': process.env.REACT_APP_CLIENT_KEY,
'x-client-secret': process.env.REACT_APP_CLIENT_SECRET
}
}
);
if (!response.ok) {
throw new Error(`Failed to load webhooks: ${response.statusText}`);
}
const webhookData = await response.json();
setWebhooks(webhookData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const getSuccessRate = (stats) => {
if (stats.total_events === 0) return 'N/A';
return `${((stats.successful_deliveries / stats.total_events) * 100).toFixed(1)}%`;
};
if (loading) return <div>Loading webhooks...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="webhook-list">
<h2>Webhooks ({webhooks.length})</h2>
{webhooks.length === 0 ? (
<p>No webhooks found. Create your first webhook to get started.</p>
) : (
<div className="webhook-grid">
{webhooks.map(webhook => (
<div key={webhook.id} className="webhook-card">
<div className="webhook-header">
<h3>{webhook.name}</h3>
<span className={`status ${webhook.isActive ? 'active' : 'inactive'}`}>
{webhook.isActive ? 'Active' : 'Inactive'}
</span>
</div>
<div className="webhook-details">
<p><strong>URL:</strong> {webhook.url}</p>
<p><strong>Encrypted:</strong> {webhook.encrypted ? 'Yes' : 'No'}</p>
<p><strong>Created:</strong> {new Date(webhook.created_at).toLocaleDateString()}</p>
</div>
<div className="webhook-stats">
<h4>Delivery Stats</h4>
<p>Total Events: {webhook.delivery_stats.total_events}</p>
<p>Success Rate: {getSuccessRate(webhook.delivery_stats)}</p>
<p>Failed: {webhook.delivery_stats.failed_deliveries}</p>
<p>Last Delivery: {
webhook.delivery_stats.last_delivery
? new Date(webhook.delivery_stats.last_delivery).toLocaleString()
: 'Never'
}</p>
</div>
</div>
))}
</div>
)}
<div className="pagination">
<button
onClick={() => setPage(p => Math.max(0, p - 1))}
disabled={page === 0}
>
Previous
</button>
<span>Page {page + 1}</span>
<button
onClick={() => setPage(p => p + 1)}
disabled={webhooks.length < pageSize}
>
Next
</button>
</div>
</div>
);
}
Python Service
import requests
from typing import List, Dict, Any, Optional
class WebhookManager:
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
def _get_headers(self) -> Dict[str, str]:
return {
'x-client-key': self.client_key,
'x-client-secret': self.client_secret
}
def list_webhooks(self, skip: int = 0, take: int = 10) -> List[Dict[str, Any]]:
"""List all webhooks with pagination"""
params = {'skip': skip, 'take': take}
response = requests.get(
f"{self.base_url}/api/v0/webhooks",
headers=self._get_headers(),
params=params
)
response.raise_for_status()
return response.json()
def get_all_webhooks(self) -> List[Dict[str, Any]]:
"""Get all webhooks by paginating through results"""
all_webhooks = []
skip = 0
take = 50
while True:
batch = self.list_webhooks(skip=skip, take=take)
if not batch:
break
all_webhooks.extend(batch)
if len(batch) < take:
break
skip += take
return all_webhooks
def get_webhook_summary(self) -> Dict[str, Any]:
"""Get summary statistics for all webhooks"""
webhooks = self.get_all_webhooks()
total_events = sum(w['delivery_stats']['total_events'] for w in webhooks)
successful_deliveries = sum(w['delivery_stats']['successful_deliveries'] for w in webhooks)
failed_deliveries = sum(w['delivery_stats']['failed_deliveries'] for w in webhooks)
return {
'total_webhooks': len(webhooks),
'active_webhooks': len([w for w in webhooks if w['isActive']]),
'inactive_webhooks': len([w for w in webhooks if not w['isActive']]),
'encrypted_webhooks': len([w for w in webhooks if w['encrypted']]),
'total_events': total_events,
'successful_deliveries': successful_deliveries,
'failed_deliveries': failed_deliveries,
'overall_success_rate': (successful_deliveries / total_events * 100) if total_events > 0 else 0
}
def find_unhealthy_webhooks(self, failure_threshold: float = 0.1) -> List[Dict[str, Any]]:
"""Find webhooks with high failure rates"""
webhooks = self.get_all_webhooks()
unhealthy = []
for webhook in webhooks:
stats = webhook['delivery_stats']
if stats['total_events'] > 0:
failure_rate = stats['failed_deliveries'] / stats['total_events']
if failure_rate > failure_threshold:
webhook['failure_rate'] = failure_rate
unhealthy.append(webhook)
return sorted(unhealthy, key=lambda w: w['failure_rate'], reverse=True)
# Usage
webhook_manager = WebhookManager('your-key', 'your-secret', 'https://api.devdraft.com')
# List webhooks with pagination
webhooks = webhook_manager.list_webhooks(skip=0, take=20)
print(f"Found {len(webhooks)} webhooks")
# Get summary
summary = webhook_manager.get_webhook_summary()
print(f"Total: {summary['total_webhooks']}, Active: {summary['active_webhooks']}")
print(f"Success Rate: {summary['overall_success_rate']:.1f}%")
# Find problematic webhooks
unhealthy = webhook_manager.find_unhealthy_webhooks(failure_threshold=0.05)
if unhealthy:
print("Webhooks with high failure rates:")
for webhook in unhealthy:
print(f"- {webhook['name']}: {webhook['failure_rate']*100:.1f}% failure rate")
Node.js/Express
const express = require('express');
const axios = require('axios');
class WebhookService {
constructor(clientKey, clientSecret, baseUrl) {
this.clientKey = clientKey;
this.clientSecret = clientSecret;
this.baseUrl = baseUrl;
}
async listWebhooks(skip = 0, take = 10) {
try {
const response = await axios.get(
`${this.baseUrl}/api/v0/webhooks`,
{
headers: {
'x-client-key': this.clientKey,
'x-client-secret': this.clientSecret
},
params: { skip, take }
}
);
return response.data;
} catch (error) {
if (error.response) {
throw new Error(`API Error: ${error.response.data.message}`);
}
throw error;
}
}
async getAllWebhooks() {
const allWebhooks = [];
let skip = 0;
const take = 50;
while (true) {
const batch = await this.listWebhooks(skip, take);
if (batch.length === 0) break;
allWebhooks.push(...batch);
if (batch.length < take) break;
skip += take;
}
return allWebhooks;
}
async getWebhookMetrics() {
const webhooks = await this.getAllWebhooks();
const metrics = webhooks.reduce((acc, webhook) => {
const stats = webhook.delivery_stats;
return {
totalWebhooks: acc.totalWebhooks + 1,
activeWebhooks: acc.activeWebhooks + (webhook.isActive ? 1 : 0),
encryptedWebhooks: acc.encryptedWebhooks + (webhook.encrypted ? 1 : 0),
totalEvents: acc.totalEvents + stats.total_events,
successfulDeliveries: acc.successfulDeliveries + stats.successful_deliveries,
failedDeliveries: acc.failedDeliveries + stats.failed_deliveries
};
}, {
totalWebhooks: 0,
activeWebhooks: 0,
encryptedWebhooks: 0,
totalEvents: 0,
successfulDeliveries: 0,
failedDeliveries: 0
});
// Calculate rates
metrics.successRate = metrics.totalEvents > 0
? (metrics.successfulDeliveries / metrics.totalEvents * 100).toFixed(2)
: '0.00';
metrics.failureRate = metrics.totalEvents > 0
? (metrics.failedDeliveries / metrics.totalEvents * 100).toFixed(2)
: '0.00';
return metrics;
}
}
// Express routes
const app = express();
const webhookService = new WebhookService(
process.env.CLIENT_KEY,
process.env.CLIENT_SECRET,
'https://api.devdraft.com'
);
// List webhooks endpoint
app.get('/webhooks', async (req, res) => {
try {
const skip = parseInt(req.query.skip) || 0;
const take = parseInt(req.query.take) || 10;
const webhooks = await webhookService.listWebhooks(skip, take);
res.json({
webhooks,
pagination: {
skip,
take,
count: webhooks.length
}
});
} catch (error) {
console.error('Error listing webhooks:', error);
res.status(500).json({ error: error.message });
}
});
// Webhook metrics endpoint
app.get('/webhooks/metrics', async (req, res) => {
try {
const metrics = await webhookService.getWebhookMetrics();
res.json(metrics);
} catch (error) {
console.error('Error getting webhook metrics:', error);
res.status(500).json({ error: error.message });
}
});
// Webhook health check endpoint
app.get('/webhooks/health', async (req, res) => {
try {
const webhooks = await webhookService.getAllWebhooks();
const health = {
healthy: 0,
warning: 0,
critical: 0,
details: []
};
webhooks.forEach(webhook => {
const stats = webhook.delivery_stats;
let status = 'healthy';
if (stats.total_events > 0) {
const failureRate = stats.failed_deliveries / stats.total_events;
if (failureRate > 0.1) {
status = 'critical';
} else if (failureRate > 0.05) {
status = 'warning';
}
}
health[status]++;
health.details.push({
id: webhook.id,
name: webhook.name,
status,
isActive: webhook.isActive,
failureRate: stats.total_events > 0
? (stats.failed_deliveries / stats.total_events * 100).toFixed(1)
: '0.0'
});
});
res.json(health);
} catch (error) {
console.error('Error checking webhook health:', error);
res.status(500).json({ error: error.message });
}
});
Pagination Best PracticesCopied!
1. Efficient Pagination
async function getAllWebhooksPaginated() {
const allWebhooks = [];
let skip = 0;
const take = 50; // Reasonable batch size
while (true) {
const batch = await fetchWebhooks(skip, take);
if (batch.length === 0) break;
allWebhooks.push(...batch);
// If we got fewer than requested, we've reached the end
if (batch.length < take) break;
skip += take;
}
return allWebhooks;
}
2. Progressive Loading
async function loadWebhooksProgressively(onBatch) {
let skip = 0;
const take = 20;
while (true) {
const batch = await fetchWebhooks(skip, take);
if (batch.length === 0) break;
// Process each batch as it arrives
await onBatch(batch);
if (batch.length < take) break;
skip += take;
}
}
// Usage
await loadWebhooksProgressively(async (webhooks) => {
console.log(`Processing batch of ${webhooks.length} webhooks`);
// Update UI or process data
});
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 ConsiderationsCopied!
-
Scoped Access: Only webhooks belonging to your authenticated application are returned
-
Data Isolation: No access to webhooks from other applications or businesses
-
Credential Security: Always use environment variables for API credentials
-
Monitoring: Regularly review webhook delivery statistics for security anomalies
Performance TipsCopied!
1. Caching
Cache webhook data when appropriate:
const webhookCache = {
data: null,
timestamp: null,
ttl: 5 * 60 * 1000 // 5 minutes
};
async function getCachedWebhooks() {
const now = Date.now();
if (webhookCache.data &&
webhookCache.timestamp &&
(now - webhookCache.timestamp) < webhookCache.ttl) {
return webhookCache.data;
}
const webhooks = await fetchWebhooks();
webhookCache.data = webhooks;
webhookCache.timestamp = now;
return webhooks;
}
2. Selective Loading
Only load the data you need:
// For overview, load basic info only
const webhookOverview = webhooks.map(w => ({
id: w.id,
name: w.name,
isActive: w.isActive,
successRate: w.delivery_stats.total_events > 0
? (w.delivery_stats.successful_deliveries / w.delivery_stats.total_events * 100).toFixed(1)
: '0'
}));