Skip to main content

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:

  1. Navigate to Developer Settings → Webhooks
  2. Click on your webhook
  3. 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:

  1. Gather Information:

    • Webhook ID
    • Delivery IDs of failed events
    • Error messages from logs
    • Your endpoint implementation details
  2. Contact Channels:

  3. Include in Your Report:

    • Description of the issue
    • Steps to reproduce
    • Expected vs actual behavior
    • Relevant log entries
    • Your webhook configuration

Community Resources

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