Fetch Webhook
The Fetch Webhook endpoint retrieves detailed information about a specific webhook configuration by its unique identifier. This endpoint provides comprehensive webhook details including delivery statistics, configuration settings, and current status.
Endpoint DetailsCopied!
-
URL:
/api/v0/webhooks/{id}
-
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.
Path ParametersCopied!
Required Parameters
Parameter |
Type |
Description |
Validation |
---|---|---|---|
|
|
Unique webhook identifier |
Must be a valid webhook ID (typically prefixed with |
Request ExamplesCopied!
Basic Request
curl -X GET 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}`, {
headers: {
'x-client-key': 'your-client-key',
'x-client-secret': 'your-client-secret'
}
});
const webhook = await response.json();
cURL with Response Headers
curl -X GET https://api.devdraft.com/api/v0/webhooks/wh_123456789 \
-H "x-client-key: your-client-key" \
-H "x-client-secret: your-client-secret" \
-i
ResponseCopied!
Success Response (200 OK)
Returns detailed webhook information 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"
}
}
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"
}
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"
}
Response FieldsCopied!
Webhook Object Fields
Field |
Type |
Description |
---|---|---|
|
|
Unique webhook identifier (prefixed with |
|
|
Human-readable webhook name |
|
|
Endpoint URL where webhook 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 |
|
|
Comprehensive 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 Status Verification
Check the current status and health of a specific webhook:
async function checkWebhookHealth(webhookId) {
try {
const webhook = await fetchWebhook(webhookId);
const stats = webhook.delivery_stats;
const successRate = stats.total_events > 0
? (stats.successful_deliveries / stats.total_events * 100).toFixed(1)
: 'N/A';
return {
id: webhook.id,
name: webhook.name,
isActive: webhook.isActive,
isHealthy: webhook.isActive && (stats.failed_deliveries / Math.max(stats.total_events, 1)) < 0.1,
successRate: `${successRate}%`,
totalEvents: stats.total_events,
failedDeliveries: stats.failed_deliveries,
lastDelivery: stats.last_delivery
? new Date(stats.last_delivery).toLocaleString()
: 'Never',
needsAttention: stats.failed_deliveries > 0 && webhook.isActive
};
} catch (error) {
console.error(`Failed to check webhook ${webhookId}:`, error);
throw error;
}
}
2. Webhook Configuration Display
Fetch webhook details for configuration management:
async function getWebhookConfiguration(webhookId) {
const webhook = await fetchWebhook(webhookId);
return {
basicInfo: {
id: webhook.id,
name: webhook.name,
url: webhook.url,
status: webhook.isActive ? 'Active' : 'Inactive',
encrypted: webhook.encrypted ? 'Yes' : 'No'
},
timestamps: {
created: new Date(webhook.created_at).toLocaleDateString(),
lastUpdated: new Date(webhook.updated_at).toLocaleDateString()
},
performance: {
totalEvents: webhook.delivery_stats.total_events,
successRate: webhook.delivery_stats.total_events > 0
? `${((webhook.delivery_stats.successful_deliveries / webhook.delivery_stats.total_events) * 100).toFixed(1)}%`
: '0%',
recentActivity: webhook.delivery_stats.last_delivery
? `Last delivery: ${new Date(webhook.delivery_stats.last_delivery).toRelativeTimeString()}`
: 'No recent activity'
}
};
}
3. Performance Monitoring
Monitor webhook performance and detect issues:
async function analyzeWebhookPerformance(webhookId) {
const webhook = await fetchWebhook(webhookId);
const stats = webhook.delivery_stats;
const analysis = {
id: webhookId,
name: webhook.name,
isActive: webhook.isActive,
metrics: {
totalEvents: stats.total_events,
successfulDeliveries: stats.successful_deliveries,
failedDeliveries: stats.failed_deliveries,
successRate: stats.total_events > 0
? (stats.successful_deliveries / stats.total_events * 100)
: 0,
failureRate: stats.total_events > 0
? (stats.failed_deliveries / stats.total_events * 100)
: 0
},
status: 'unknown',
recommendations: []
};
// Determine status and recommendations
if (!webhook.isActive) {
analysis.status = 'inactive';
analysis.recommendations.push('Webhook is inactive - activate to receive events');
} else if (stats.total_events === 0) {
analysis.status = 'no_activity';
analysis.recommendations.push('No events sent yet - webhook is ready but unused');
} else if (analysis.metrics.failureRate > 20) {
analysis.status = 'critical';
analysis.recommendations.push('High failure rate detected - check endpoint health');
analysis.recommendations.push('Verify webhook URL is accessible and returning 2xx responses');
} else if (analysis.metrics.failureRate > 5) {
analysis.status = 'warning';
analysis.recommendations.push('Moderate failure rate - monitor endpoint performance');
} else {
analysis.status = 'healthy';
analysis.recommendations.push('Webhook is performing well');
}
return analysis;
}
4. Webhook Troubleshooting
Gather information for troubleshooting webhook issues:
async function troubleshootWebhook(webhookId) {
try {
const webhook = await fetchWebhook(webhookId);
const troubleshooting = {
webhook: {
id: webhook.id,
name: webhook.name,
url: webhook.url,
isActive: webhook.isActive,
encrypted: webhook.encrypted
},
issues: [],
suggestions: []
};
// Check for common issues
if (!webhook.isActive) {
troubleshooting.issues.push('Webhook is disabled');
troubleshooting.suggestions.push('Enable webhook to receive events');
}
if (webhook.delivery_stats.failed_deliveries > 0) {
const failureRate = (webhook.delivery_stats.failed_deliveries / webhook.delivery_stats.total_events) * 100;
troubleshooting.issues.push(`${webhook.delivery_stats.failed_deliveries} failed deliveries (${failureRate.toFixed(1)}% failure rate)`);
troubleshooting.suggestions.push('Check if webhook endpoint is accessible');
troubleshooting.suggestions.push('Verify endpoint returns 2xx HTTP status codes');
troubleshooting.suggestions.push('Check for network connectivity issues');
}
if (!webhook.url.startsWith('https://')) {
troubleshooting.issues.push('Webhook URL is not using HTTPS');
troubleshooting.suggestions.push('Use HTTPS for webhook URLs in production');
}
// Check if webhook hasn't received events recently
if (webhook.delivery_stats.last_delivery) {
const lastDelivery = new Date(webhook.delivery_stats.last_delivery);
const hoursSinceLastDelivery = (Date.now() - lastDelivery.getTime()) / (1000 * 60 * 60);
if (hoursSinceLastDelivery > 24) {
troubleshooting.issues.push(`No events delivered in ${Math.floor(hoursSinceLastDelivery)} hours`);
troubleshooting.suggestions.push('Check if events are being triggered in your application');
}
}
return troubleshooting;
} catch (error) {
return {
webhook: null,
issues: [`Failed to fetch webhook: ${error.message}`],
suggestions: ['Verify webhook ID is correct', 'Check API credentials and permissions']
};
}
}
Integration ExamplesCopied!
React Component
import React, { useState, useEffect } from 'react';
function WebhookDetails({ webhookId }) {
const [webhook, setWebhook] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (webhookId) {
loadWebhook();
}
}, [webhookId]);
const loadWebhook = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/v0/webhooks/${webhookId}`, {
headers: {
'x-client-key': process.env.REACT_APP_CLIENT_KEY,
'x-client-secret': process.env.REACT_APP_CLIENT_SECRET
}
});
if (!response.ok) {
if (response.status === 404) {
throw new Error('Webhook not found');
}
throw new Error(`Failed to load webhook: ${response.statusText}`);
}
const webhookData = await response.json();
setWebhook(webhookData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const getStatusColor = (isActive) => {
return isActive ? '#28a745' : '#6c757d';
};
const getSuccessRate = (stats) => {
if (stats.total_events === 0) return 'N/A';
return `${((stats.successful_deliveries / stats.total_events) * 100).toFixed(1)}%`;
};
const getHealthStatus = (webhook) => {
if (!webhook.isActive) return { status: 'Inactive', color: '#6c757d' };
const stats = webhook.delivery_stats;
if (stats.total_events === 0) return { status: 'No Activity', color: '#ffc107' };
const failureRate = stats.failed_deliveries / stats.total_events;
if (failureRate > 0.1) return { status: 'Unhealthy', color: '#dc3545' };
if (failureRate > 0.05) return { status: 'Warning', color: '#fd7e14' };
return { status: 'Healthy', color: '#28a745' };
};
if (loading) return <div className="loading">Loading webhook details...</div>;
if (error) return <div className="error">Error: {error}</div>;
if (!webhook) return <div className="not-found">Webhook not found</div>;
const health = getHealthStatus(webhook);
return (
<div className="webhook-details">
<div className="webhook-header">
<h2>{webhook.name}</h2>
<div className="status-badges">
<span
className="status-badge"
style={{ backgroundColor: getStatusColor(webhook.isActive) }}
>
{webhook.isActive ? 'Active' : 'Inactive'}
</span>
<span
className="health-badge"
style={{ backgroundColor: health.color }}
>
{health.status}
</span>
</div>
</div>
<div className="webhook-info">
<div className="info-section">
<h3>Configuration</h3>
<div className="info-grid">
<div className="info-item">
<label>ID:</label>
<span className="monospace">{webhook.id}</span>
</div>
<div className="info-item">
<label>URL:</label>
<span className="url">{webhook.url}</span>
</div>
<div className="info-item">
<label>Encrypted:</label>
<span>{webhook.encrypted ? 'Yes' : 'No'}</span>
</div>
<div className="info-item">
<label>Created:</label>
<span>{new Date(webhook.created_at).toLocaleString()}</span>
</div>
<div className="info-item">
<label>Last Updated:</label>
<span>{new Date(webhook.updated_at).toLocaleString()}</span>
</div>
</div>
</div>
<div className="info-section">
<h3>Delivery Statistics</h3>
<div className="stats-grid">
<div className="stat-item">
<div className="stat-value">{webhook.delivery_stats.total_events}</div>
<div className="stat-label">Total Events</div>
</div>
<div className="stat-item">
<div className="stat-value">{webhook.delivery_stats.successful_deliveries}</div>
<div className="stat-label">Successful</div>
</div>
<div className="stat-item">
<div className="stat-value">{webhook.delivery_stats.failed_deliveries}</div>
<div className="stat-label">Failed</div>
</div>
<div className="stat-item">
<div className="stat-value">{getSuccessRate(webhook.delivery_stats)}</div>
<div className="stat-label">Success Rate</div>
</div>
<div className="stat-item">
<div className="stat-value">
{webhook.delivery_stats.last_delivery
? new Date(webhook.delivery_stats.last_delivery).toLocaleDateString()
: 'Never'
}
</div>
<div className="stat-label">Last Delivery</div>
</div>
</div>
</div>
</div>
<div className="webhook-actions">
<button onClick={loadWebhook} className="btn-secondary">
Refresh
</button>
<button
onClick={() => window.open(`/webhooks/${webhook.id}/edit`, '_blank')}
className="btn-primary"
>
Edit Webhook
</button>
</div>
</div>
);
}
Python Service
import requests
from typing import Dict, Any, Optional
from datetime import datetime, timedelta
class WebhookInspector:
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 get_webhook(self, webhook_id: str) -> Optional[Dict[str, Any]]:
"""Fetch a specific webhook by ID"""
try:
response = requests.get(
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:
return None
else:
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"Error fetching webhook {webhook_id}: {e}")
raise
def analyze_webhook_health(self, webhook_id: str) -> Dict[str, Any]:
"""Analyze webhook health and performance"""
webhook = self.get_webhook(webhook_id)
if not webhook:
return {
'webhook_id': webhook_id,
'status': 'not_found',
'health_score': 0,
'issues': ['Webhook not found'],
'recommendations': ['Verify webhook ID is correct']
}
stats = webhook['delivery_stats']
issues = []
recommendations = []
health_score = 100 # Start with perfect score
# Check if webhook is active
if not webhook['isActive']:
issues.append('Webhook is inactive')
recommendations.append('Activate webhook to receive events')
health_score -= 50
# Check delivery success rate
if stats['total_events'] > 0:
failure_rate = stats['failed_deliveries'] / stats['total_events']
if failure_rate > 0.2: # > 20% failure rate
issues.append(f"High failure rate: {failure_rate*100:.1f}%")
recommendations.append('Check endpoint availability and response codes')
health_score -= 30
elif failure_rate > 0.05: # > 5% failure rate
issues.append(f"Moderate failure rate: {failure_rate*100:.1f}%")
recommendations.append('Monitor endpoint performance')
health_score -= 15
# Check for recent activity
if stats['last_delivery']:
last_delivery = datetime.fromisoformat(stats['last_delivery'].replace('Z', '+00:00'))
hours_since_last = (datetime.now().astimezone() - last_delivery).total_seconds() / 3600
if hours_since_last > 48:
issues.append(f"No activity for {int(hours_since_last)} hours")
recommendations.append('Verify events are being triggered')
health_score -= 10
# Check URL security
if not webhook['url'].startswith('https://'):
issues.append('Using insecure HTTP protocol')
recommendations.append('Use HTTPS for webhook URLs')
health_score -= 10
# Determine overall status
if health_score >= 90:
status = 'excellent'
elif health_score >= 70:
status = 'good'
elif health_score >= 50:
status = 'warning'
else:
status = 'critical'
return {
'webhook_id': webhook_id,
'webhook_name': webhook['name'],
'status': status,
'health_score': max(0, health_score),
'is_active': webhook['isActive'],
'encrypted': webhook['encrypted'],
'total_events': stats['total_events'],
'success_rate': (stats['successful_deliveries'] / max(stats['total_events'], 1)) * 100,
'failure_rate': (stats['failed_deliveries'] / max(stats['total_events'], 1)) * 100,
'last_delivery': stats['last_delivery'],
'issues': issues,
'recommendations': recommendations
}
def get_webhook_summary(self, webhook_id: str) -> Dict[str, Any]:
"""Get a summary of webhook information"""
webhook = self.get_webhook(webhook_id)
if not webhook:
return {
'found': False,
'webhook_id': webhook_id
}
stats = webhook['delivery_stats']
return {
'found': True,
'webhook_id': webhook['id'],
'name': webhook['name'],
'url': webhook['url'],
'is_active': webhook['isActive'],
'encrypted': webhook['encrypted'],
'created_at': webhook['created_at'],
'updated_at': webhook['updated_at'],
'performance': {
'total_events': stats['total_events'],
'successful_deliveries': stats['successful_deliveries'],
'failed_deliveries': stats['failed_deliveries'],
'success_rate_percent': (stats['successful_deliveries'] / max(stats['total_events'], 1)) * 100,
'last_delivery': stats['last_delivery']
}
}
# Usage
inspector = WebhookInspector('your-key', 'your-secret', 'https://api.devdraft.com')
# Get webhook details
webhook = inspector.get_webhook('wh_123456789')
if webhook:
print(f"Webhook: {webhook['name']}")
print(f"Status: {'Active' if webhook['isActive'] else 'Inactive'}")
print(f"Total Events: {webhook['delivery_stats']['total_events']}")
# Analyze webhook health
health = inspector.analyze_webhook_health('wh_123456789')
print(f"Health Score: {health['health_score']}/100")
print(f"Status: {health['status']}")
if health['issues']:
print("Issues:", ', '.join(health['issues']))
Node.js/Express Utility
const axios = require('axios');
class WebhookInspector {
constructor(clientKey, clientSecret, baseUrl) {
this.clientKey = clientKey;
this.clientSecret = clientSecret;
this.baseUrl = baseUrl;
}
async getWebhook(webhookId) {
try {
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;
} catch (error) {
if (error.response?.status === 404) {
return null;
}
throw new Error(`Failed to fetch webhook: ${error.response?.data?.message || error.message}`);
}
}
async validateWebhookEndpoint(webhook) {
try {
// Attempt to reach the webhook endpoint
const response = await axios.head(webhook.url, {
timeout: 10000,
validateStatus: () => true // Don't throw on non-2xx status
});
return {
reachable: response.status < 500,
statusCode: response.status,
responseTime: response.headers['x-response-time'] || 'unknown'
};
} catch (error) {
return {
reachable: false,
error: error.code || error.message,
statusCode: null
};
}
}
async getWebhookDiagnostics(webhookId) {
const webhook = await this.getWebhook(webhookId);
if (!webhook) {
return {
found: false,
webhookId,
error: 'Webhook not found'
};
}
const stats = webhook.delivery_stats;
const endpointCheck = await this.validateWebhookEndpoint(webhook);
// Calculate metrics
const totalEvents = stats.total_events;
const successRate = totalEvents > 0 ? (stats.successful_deliveries / totalEvents) * 100 : 0;
const failureRate = totalEvents > 0 ? (stats.failed_deliveries / totalEvents) * 100 : 0;
// Determine health status
let healthStatus = 'unknown';
if (!webhook.isActive) {
healthStatus = 'inactive';
} else if (totalEvents === 0) {
healthStatus = 'no-activity';
} else if (failureRate > 20) {
healthStatus = 'critical';
} else if (failureRate > 5) {
healthStatus = 'warning';
} else {
healthStatus = 'healthy';
}
return {
found: true,
webhook: {
id: webhook.id,
name: webhook.name,
url: webhook.url,
isActive: webhook.isActive,
encrypted: webhook.encrypted,
createdAt: webhook.created_at,
updatedAt: webhook.updated_at
},
performance: {
totalEvents,
successfulDeliveries: stats.successful_deliveries,
failedDeliveries: stats.failed_deliveries,
successRate: Math.round(successRate * 100) / 100,
failureRate: Math.round(failureRate * 100) / 100,
lastDelivery: stats.last_delivery
},
health: {
status: healthStatus,
endpointReachable: endpointCheck.reachable,
endpointStatus: endpointCheck.statusCode,
lastChecked: new Date().toISOString()
},
recommendations: this.generateRecommendations(webhook, stats, endpointCheck)
};
}
generateRecommendations(webhook, stats, endpointCheck) {
const recommendations = [];
if (!webhook.isActive) {
recommendations.push('Activate webhook to start receiving events');
}
if (!endpointCheck.reachable) {
recommendations.push('Webhook endpoint is not reachable - check URL and network connectivity');
}
if (stats.failed_deliveries > 0 && webhook.isActive) {
const failureRate = (stats.failed_deliveries / stats.total_events) * 100;
if (failureRate > 10) {
recommendations.push('High failure rate detected - verify endpoint returns 2xx status codes');
}
}
if (!webhook.url.startsWith('https://')) {
recommendations.push('Use HTTPS for webhook URLs in production environments');
}
if (!webhook.encrypted && webhook.url.startsWith('https://')) {
recommendations.push('Consider enabling payload encryption for additional security');
}
if (stats.last_delivery) {
const lastDelivery = new Date(stats.last_delivery);
const hoursSince = (Date.now() - lastDelivery.getTime()) / (1000 * 60 * 60);
if (hoursSince > 24 && webhook.isActive) {
recommendations.push('No recent activity - verify events are being triggered');
}
}
if (recommendations.length === 0) {
recommendations.push('Webhook is configured correctly and performing well');
}
return recommendations;
}
}
module.exports = WebhookInspector;
// Usage example
const inspector = new WebhookInspector(
process.env.CLIENT_KEY,
process.env.CLIENT_SECRET,
'https://api.devdraft.com'
);
// Express endpoint for webhook diagnostics
app.get('/webhooks/:id/diagnostics', async (req, res) => {
try {
const diagnostics = await inspector.getWebhookDiagnostics(req.params.id);
res.json(diagnostics);
} catch (error) {
console.error('Webhook diagnostics error:', error);
res.status(500).json({ error: error.message });
}
});
Security ConsiderationsCopied!
Access Control
-
Only webhooks belonging to your authenticated application are accessible
-
API key scoping ensures proper authorization
-
No cross-application webhook access
Data Protection
-
Webhook delivery statistics don't include sensitive payload data
-
Signing secrets are not returned in API responses
-
Encrypted webhook configuration is indicated but encryption keys are not exposed
Best Practices
-
Always validate webhook ID format before making requests
-
Implement proper error handling for different response scenarios
-
Use HTTPS for webhook endpoints in production
-
Monitor webhook health regularly using this endpoint
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
Common Integration PatternsCopied!
1. Webhook Health Dashboard
Regular health checks for monitoring dashboards
2. Troubleshooting Tools
Detailed webhook inspection for support and debugging
3. Performance Analytics
Historical performance tracking and trend analysis