Webhook Troubleshooting
Common Issues
Webhook Not Receiving Events
Symptoms
- No webhook requests reaching your endpoint
- Events occurring but no corresponding webhook deliveries
- Webhook status shows as active but no activity
Possible Causes & Solutions
1. Company Authorization Missing
Check if the webhook is authorized for the relevant companies:
GET https://deepsy.fr/api/v1/companies/{company_id}/webhooks
Authorization: Bearer deepsy-dev-your-api-key-here
Solution: Company administrators must authorize the webhook:
POST https://deepsy.fr/api/v1/companies/{company_id}/webhooks/{webhook_id}/authorize
Authorization: Bearer deepsy-dev-your-api-key-here
Content-Type: application/json
{
"events": ["candidate.test_completed", "email.delivered"]
}
2. Incorrect Event Subscription
Verify your webhook is subscribed to the correct events:
GET https://deepsy.fr/api/v1/webhooks/{webhook_id}
Authorization: Bearer deepsy-dev-your-api-key-here
Solution: Update event subscriptions:
PUT https://deepsy.fr/api/v1/webhooks/{webhook_id}
Authorization: Bearer deepsy-dev-your-api-key-here
Content-Type: application/json
{
"events": ["candidate.test_completed", "candidate.evaluation_completed"]
}
3. Webhook URL Not Accessible
Test your webhook URL accessibility:
# Test with curl
curl -X POST https://your-domain.com/webhooks/deepsy \
-H "Content-Type: application/json" \
-d '{"test": true}'
# Should return HTTP 200
Common URL Issues:
- URL not publicly accessible (localhost, private networks)
- HTTPS certificate issues
- Firewall blocking requests
- DNS resolution problems
4. Webhook Status Issues
Check webhook status:
GET https://deepsy.fr/api/v1/webhooks/{webhook_id}
Authorization: Bearer deepsy-dev-your-api-key-here
If status is inactive
or suspended
, reactivate:
PUT https://deepsy.fr/api/v1/webhooks/{webhook_id}
Authorization: Bearer deepsy-dev-your-api-key-here
Content-Type: application/json
{
"status": "active"
}
Signature Verification Failures
Symptoms
- Webhook requests arriving but signature verification fails
- HTTP 401 responses from your endpoint
- "Invalid signature" errors in logs
Debugging Steps
1. Verify Secret Format
Ensure your secret starts with whsec_
:
// ✅ Correct format
const secret = 'whsec_abc123def456...';
// ❌ Wrong - missing prefix
const secret = 'abc123def456...';
2. Check Signature Header
Verify the signature header format:
// Expected format: t=timestamp,v1=signature
const signature = req.headers['x-deepsy-signature'];
console.log('Signature header:', signature);
// Should look like: t=1642248000,v1=5257a869e7ec...
3. Debug Signature Calculation
Add debug logging to your signature verification:
function verifyWebhookSignature(payload, signature, secret) {
console.log('=== Signature Debug ===');
console.log('Payload:', payload);
console.log('Signature header:', signature);
console.log('Secret (first 10 chars):', secret.substring(0, 10));
const elements = signature.split(',');
const timestamp = elements.find(el => el.startsWith('t=')).split('=')[1];
const hash = elements.find(el => el.startsWith('v1=')).split('=')[1];
console.log('Extracted timestamp:', timestamp);
console.log('Extracted hash:', hash);
const signedPayload = timestamp + '.' + payload;
console.log('Signed payload:', signedPayload);
const expectedHash = crypto
.createHmac('sha256', secret)
.update(signedPayload, 'utf8')
.digest('hex');
console.log('Expected hash:', expectedHash);
console.log('Received hash:', hash);
console.log('Hashes match:', expectedHash === hash);
return crypto.timingSafeEqual(
Buffer.from(hash, 'hex'),
Buffer.from(expectedHash, 'hex')
);
}
4. Common Signature Issues
- Encoding Problems: Ensure payload is treated as UTF-8 string
- Timestamp Tolerance: Default tolerance is 5 minutes
- Secret Regeneration: Old secret still being used after regeneration
Events Being Retried Repeatedly
Symptoms
- Same event delivered multiple times
- Webhook logs show multiple delivery attempts
- High retry counts in webhook logs
Causes & Solutions
1. Endpoint Not Returning HTTP 200
Your endpoint must return exactly HTTP 200 for successful processing:
// ✅ Correct
app.post('/webhooks/deepsy', (req, res) => {
try {
processWebhookEvent(req.body);
res.status(200).send('OK'); // Must be 200
} catch (error) {
console.error('Processing error:', error);
res.status(200).send('OK'); // Still return 200 if event was received
}
});
// ❌ Wrong - causes retries
app.post('/webhooks/deepsy', (req, res) => {
try {
processWebhookEvent(req.body);
res.status(201).send('Created'); // 201 triggers retry
} catch (error) {
res.status(500).send('Error'); // 500 triggers retry
}
});
2. Endpoint Timeout
Webhooks must respond within 30 seconds:
// ✅ Process quickly or queue for background processing
app.post('/webhooks/deepsy', (req, res) => {
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
setImmediate(() => {
processWebhookEvent(req.body);
});
});
// ❌ Long processing blocks response
app.post('/webhooks/deepsy', (req, res) => {
processLongRunningTask(req.body); // Takes 60 seconds
res.status(200).send('OK'); // Too late - already timed out
});
3. Intermittent Failures
Handle temporary failures gracefully:
app.post('/webhooks/deepsy', (req, res) => {
// Always acknowledge receipt first
res.status(200).send('OK');
try {
processWebhookEvent(req.body);
} catch (error) {
// Log error but don't fail the webhook
console.error('Webhook processing error:', error);
// Queue for retry in your own system if needed
queueForRetry(req.body);
}
});
Duplicate Event Processing
Symptoms
- Same event processed multiple times
- Duplicate records in your database
- Business logic executed repeatedly for same event
Solutions
1. Implement Idempotency
Use delivery IDs to prevent duplicate processing:
const processedDeliveries = new Set(); // Use Redis in production
app.post('/webhooks/deepsy', (req, res) => {
const deliveryId = req.body.metadata.delivery_id;
if (processedDeliveries.has(deliveryId)) {
console.log('Duplicate delivery ignored:', deliveryId);
return res.status(200).send('OK');
}
processedDeliveries.add(deliveryId);
processWebhookEvent(req.body);
res.status(200).send('OK');
});
2. Database-based Deduplication
async function processWebhookEvent(event) {
const deliveryId = event.metadata.delivery_id;
// Check if already processed
const existing = await db.webhookDeliveries.findOne({ deliveryId });
if (existing) {
console.log('Event already processed:', deliveryId);
return;
}
// Mark as processing
await db.webhookDeliveries.create({
deliveryId,
eventType: event.event_type,
processedAt: new Date(),
status: 'processing'
});
try {
// Process the event
await handleEvent(event);
// Mark as completed
await db.webhookDeliveries.updateOne(
{ deliveryId },
{ status: 'completed' }
);
} catch (error) {
// Mark as failed
await db.webhookDeliveries.updateOne(
{ deliveryId },
{ status: 'failed', error: error.message }
);
throw error;
}
}
High Latency Issues
Symptoms
- Webhook processing taking too long
- Timeouts from DeePsy
- Poor user experience due to delayed processing
Solutions
1. Asynchronous Processing
const Queue = require('bull');
const webhookQueue = new Queue('webhook processing');
app.post('/webhooks/deepsy', (req, res) => {
// Respond immediately
res.status(200).send('OK');
// Queue for background processing
webhookQueue.add('process-webhook', req.body, {
attempts: 3,
backoff: 'exponential'
});
});
webhookQueue.process('process-webhook', async (job) => {
const event = job.data;
await processWebhookEvent(event);
});
2. Optimize Processing Logic
// ✅ Efficient processing
async function processWebhookEvent(event) {
switch (event.event_type) {
case 'candidate.test_completed':
// Only update necessary fields
await updateCandidateStatus(event.data.candidate_id, 'completed');
break;
case 'email.delivered':
// Batch email status updates
await batchUpdateEmailStatus([event.data]);
break;
}
}
// ❌ Inefficient processing
async function processWebhookEvent(event) {
// Don't fetch unnecessary data
const fullCandidate = await fetchFullCandidateData(event.data.candidate_id);
const allCampaigns = await fetchAllCampaigns();
// ... lots of unnecessary processing
}
Debugging Tools
Webhook Logs API
Use the webhook logs API to debug issues:
GET https://deepsy.fr/api/v1/webhooks/logs/list?webhook_id={webhook_id}
Authorization: Bearer deepsy-dev-your-api-key-here
Log Analysis
// Analyze webhook logs
function analyzeWebhookLogs(logs) {
const analysis = {
totalDeliveries: logs.length,
successfulDeliveries: 0,
failedDeliveries: 0,
averageResponseTime: 0,
commonErrors: {},
statusCodes: {}
};
logs.forEach(log => {
if (log.status === 'success') {
analysis.successfulDeliveries++;
} else {
analysis.failedDeliveries++;
// Track common errors
const error = log.error_message || 'Unknown error';
analysis.commonErrors[error] = (analysis.commonErrors[error] || 0) + 1;
}
// Track status codes
const code = log.http_status_code || 'No response';
analysis.statusCodes[code] = (analysis.statusCodes[code] || 0) + 1;
// Calculate average response time
if (log.response_time_ms) {
analysis.averageResponseTime += log.response_time_ms;
}
});
analysis.averageResponseTime = analysis.averageResponseTime / logs.length;
analysis.successRate = (analysis.successfulDeliveries / analysis.totalDeliveries) * 100;
return analysis;
}
Health Check Implementation
// Webhook health check endpoint
app.get('/webhooks/health', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
checks: {
database: await checkDatabase(),
queue: await checkQueue(),
memory: checkMemoryUsage(),
uptime: process.uptime()
}
};
const isHealthy = Object.values(health.checks).every(check =>
check.status === 'ok'
);
res.status(isHealthy ? 200 : 503).json(health);
});
async function checkDatabase() {
try {
await db.ping();
return { status: 'ok', responseTime: '< 100ms' };
} catch (error) {
return { status: 'error', error: error.message };
}
}
Getting Help
Webhook Logs Dashboard
Access detailed webhook logs in your DeePsy dashboard:
- Navigate to Developer Settings → Webhooks
- Click on your webhook
- View the Logs tab for delivery details
Log Information Includes:
- Delivery timestamp
- HTTP status code
- Response time
- Error messages
- Retry attempts
- Full request/response details
Contact Support
If you're still experiencing issues:
-
Gather Information:
- Webhook ID
- Delivery IDs of failed events
- Error messages from logs
- Your endpoint implementation details
-
Contact Channels:
- Technical support: dev@deepsy.fr
- General support: deepsy.fr/contact
-
Include in Your Report:
- Description of the issue
- Steps to reproduce
- Expected vs actual behavior
- Relevant log entries
- Your webhook configuration
Community Resources
- Check our API documentation for endpoint details
- Review other webhook guides for best practices
- Join our developer community for peer support
Prevention Tips
Monitoring Setup
// Set up monitoring alerts
const alertThresholds = {
failureRate: 5, // Alert if > 5% failure rate
responseTime: 5000, // Alert if > 5 second response time
errorCount: 10 // Alert if > 10 errors in 5 minutes
};
function checkWebhookHealth() {
const metrics = getWebhookMetrics();
if (metrics.failureRate > alertThresholds.failureRate) {
sendAlert('High webhook failure rate', metrics);
}
if (metrics.averageResponseTime > alertThresholds.responseTime) {
sendAlert('Slow webhook response time', metrics);
}
}
// Run health check every 5 minutes
setInterval(checkWebhookHealth, 5 * 60 * 1000);
Best Practices Checklist
- ✅ Always return HTTP 200 for received events
- ✅ Implement signature verification
- ✅ Handle events idempotently
- ✅ Process events asynchronously if needed
- ✅ Monitor webhook performance
- ✅ Set up alerting for failures
- ✅ Keep webhook secrets secure
- ✅ Test webhook endpoints regularly
- ✅ Log webhook events for debugging
- ✅ Implement proper error handling