Quick solutions for frequently encountered webhook problems including signature verification, payload parsing, and connectivity issues.
❌ Signature verification failed
❌ Unauthorized (401)❌ No webhooks received
❌ Events triggering but no deliverycurl -X GET https://api-sandbox.fndev.net/api/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Verify status: "active" in response.
curl -X POST https://your-endpoint.com/webhooks \
-H "Content-Type: application/json" \
-d '{"test": "data"}'Should return 200 OK.
curl -v https://your-endpoint.com/webhooksLook for SSL certificate verify ok.
curl -X GET "https://api-sandbox.fndev.net/api/v1/webhooks/delivery-logs?webhookId=wh_abc123" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Look for error patterns in failed deliveries.
Ensure webhook is subscribed to the events you're triggering:
const webhook = await getWebhook('wh_abc123');
console.log('Subscribed events:', webhook.result.events);❌ Request timeout after 30 seconds
❌ Connection timeoutRespond immediately, process asynchronously:
// ❌ Wrong - slow synchronous processing
app.post('/webhooks', async (req, res) => {
await verifySignature(req);
await syncToSalesforce(req.body); // Slow operation
await updateDatabase(req.body); // Slow operation
await sendEmail(req.body); // Slow operation
res.status(200).send('OK'); // Too late!
});
// ✅ Correct - respond immediately
app.post('/webhooks', async (req, res) => {
await verifySignature(req);
// Respond immediately
res.status(200).send('OK');
// Process asynchronously
processWebhookAsync(req.body)
.catch(error => console.error('Processing error:', error));
});❌ Same event received multiple times
❌ Duplicate processingImplement idempotency using eventId:
const processedEvents = new Set(); // Or Redis/Database
app.post('/webhooks', async (req, res) => {
const payload = JSON.parse(req.body.toString());
const { eventId } = payload;
// Check if already processed
if (processedEvents.has(eventId)) {
console.log(`Duplicate event ${eventId}, skipping`);
return res.status(200).send('Already processed');
}
// Mark as processing
processedEvents.add(eventId);
try {
// Process event
await handleEvent(payload);
res.status(200).send('OK');
} catch (error) {
// Remove on failure to allow retry
processedEvents.delete(eventId);
throw error;
}
});❌ SyntaxError: Unexpected token
❌ Invalid JSONHandle raw body correctly:
// For signature verification
app.use(express.raw({ type: 'application/json' }));
app.post('/webhooks', (req, res) => {
try {
// 1. Verify signature with raw body
const signature = req.headers['x-fn-signature'];
verifySignature(req.body, signature, secret);
// 2. Parse JSON
const payload = JSON.parse(req.body.toString());
// 3. Process
processWebhook(payload);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(400).send('Bad request');
}
});Issue: ngrok tunnel not accessible
# ❌ Problem
Error: ngrok tunnel not found
# ✅ Solution
ngrok http 3000 --log=stdoutIssue: ngrok free tier URL changes
# ✅ Solution: Use ngrok authtoken for persistent URLs
ngrok authtoken YOUR_TOKEN
ngrok http 3000 --hostname=your-subdomain.ngrok-free.appIssue: Firewall blocking Field Nation IPs
Solution: Whitelist Field Nation IPs:
Sandbox:
18.215.51.196
3.223.100.250
44.199.193.222Production: Contact Field Nation support
Webhooks retrying when they shouldn't.
Return appropriate status codes:
app.post('/webhooks', async (req, res) => {
try {
// Signature verification failure
if (!verifySignature(req)) {
return res.status(401).send('Unauthorized'); // Won't retry
}
// Invalid payload format
if (!validatePayload(req.body)) {
return res.status(400).send('Bad request'); // Won't retry
}
// Process webhook
await processEvent(req.body);
// Success
return res.status(200).send('OK');
} catch (error) {
if (error.retriable) {
// Temporary issue - allow retry
return res.status(500).send('Internal error');
} else {
// Permanent issue - don't retry
return res.status(422).send('Unprocessable');
}
}
});| Status | Retry? | Use Case |
|---|---|---|
| 200-299 | No | Success |
| 400 | No | Invalid request |
| 401 | No | Invalid signature |
| 404 | No | Endpoint not found |
| 410 | No | Endpoint gone |
| 422 | No | Unprocessable |
| 500-599 | Yes | Server error |
| Timeout | Yes | Network issue |
When webhooks aren't working, check:
activeLast updated on