Best Practices
Follow these guidelines to build a reliable webhook integration.
Respond Quickly
Your endpoint must respond within 30 seconds. If processing takes longer, accept the webhook immediately and process the event asynchronously:
app.post('/webhooks/zyphr', async (req, res) => {
// Respond immediately
res.status(200).json({ received: true });
// Process asynchronously
await processWebhookEvent(req.body);
});
Always Verify Signatures
Never process a webhook payload without verifying the signature first. Use timing-safe comparison functions to prevent timing attacks.
See Security & Signatures for verification examples in TypeScript, Python, Go, and PHP.
Handle Duplicates
The same event may be delivered more than once (e.g., if your endpoint returns a timeout after processing). Use the webhook-id header (Standard Webhooks format) or the event's id field to deduplicate:
app.post('/webhooks/zyphr', async (req, res) => {
const msgId = req.headers['webhook-id'];
// Check if we've already processed this message
const alreadyProcessed = await cache.exists(`webhook:${msgId}`);
if (alreadyProcessed) {
return res.status(200).json({ received: true });
}
// Process and mark as handled
await processEvent(req.body);
await cache.set(`webhook:${msgId}`, '1', 'EX', 86400); // 24h TTL
res.status(200).json({ received: true });
});
The msg_id is deterministic — the same event delivered to the same webhook always produces the same msg_id, making it safe to use as an idempotency key.
Return Appropriate Status Codes
| Your Response | What Zyphr Does |
|---|---|
2xx | Marks delivery as success, resets failure counter |
4xx | Treats as a permanent failure — still retried (fix your endpoint) |
5xx | Treats as a transient failure — retried per retry policy |
| Timeout (>30s) | Treats as a failure — retried per retry policy |
Return 200 as quickly as possible. Don't return 4xx to signal "I don't want this event" — instead, update your webhook's event subscriptions.
Use HTTPS
Always use HTTPS endpoints. Keep your SSL/TLS certificates valid — expired certificates will cause delivery failures.
Monitor Your Endpoints
- Check your webhook's
consecutive_failurescount regularly - Watch for
disabledstatus — auto-disable triggers after 10 consecutive failures - Review delivery logs to identify patterns in failures
- Set up alerting on your side when webhook processing fails
Handle Out-of-Order Events
Events may arrive out of order. For example, you might receive email.opened before email.delivered. Design your event handlers to be order-independent:
- Use timestamps within the event
datato determine event chronology - Don't assume events arrive in the order they occurred
- Use the event
idfor correlation, not delivery order
Plan Limits
Webhook endpoint limits vary by plan:
| Plan | Max Webhooks |
|---|---|
| Free | 2 |
| Starter | 10 |
| Pro | 50 |
| Scale | Unlimited |
| Enterprise | Unlimited |
Webhook delivery attempts are included in all plans at no additional cost. If you hit your endpoint limit, delete unused webhooks or upgrade your plan.
Summary
- Respond within 30 seconds — process events asynchronously
- Always verify signatures with timing-safe comparison
- Deduplicate using
webhook-idor eventid - Return
2xximmediately, handle errors on your side - Use HTTPS with valid certificates
- Monitor endpoint health and watch for auto-disable
- Design for out-of-order delivery