Webhooks are powerful, but implementing them correctly requires attention to security and reliability. In this guide, we’ll cover the essential best practices for building robust webhook handlers with httpied.
Security First
Always Verify Webhook Signatures
Never trust incoming webhook requests blindly. httpied signs all webhook requests with HMAC-SHA256, and you should always verify these signatures:
import hmac import hashlib
def verify_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Httpied-Signature')
payload = request.get_data(as_text=True)
if not verify_signature(payload, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
# Process webhook...
return 'OK', 200 Use HTTPS Only
Always use HTTPS endpoints for your webhooks. httpied will refuse to send webhooks to non-HTTPS endpoints in production.
# Good client.enqueue(target="https://api.example.com/webhook")
# Bad - will fail in production
client.enqueue(target="http://api.example.com/webhook") Implement IP Allowlisting
For extra security, allowlist httpied’s IP ranges in your firewall. You can find the current IP ranges in your dashboard under Settings → Security.
Reliability and Idempotency
Make Handlers Idempotent
httpied will retry failed webhooks, so your handlers must be idempotent. The same webhook might be delivered multiple times:
@app.route('/webhook', methods=['POST']) def handle_webhook():
data = request.json
webhook_id = request.headers.get('X-Httpied-Delivery-Id')
# Check if we've already processed this delivery
if DeliveryLog.exists(webhook_id):
return 'Already processed', 200
# Process the webhook
process_payment(data['payment_id'])
# Record that we've processed this delivery
DeliveryLog.create(webhook_id)
return 'OK', 200 Return Proper HTTP Status Codes
Use HTTP status codes correctly to help httpied understand what happened:
- 2xx (Success): Webhook was processed successfully. httpied won’t retry.
- 4xx (Client Error): Permanent failure. httpied won’t retry (except 429).
- 5xx (Server Error): Temporary failure. httpied will retry with exponential backoff.
- 429 (Rate Limited): Special case. httpied will respect Retry-After headers.
@app.route('/webhook', methods=['POST']) def handle_webhook():
try:
data = request.json
# Validation error - don't retry
if not data.get('user_id'):
return 'Missing user_id', 400
# Process webhook
process_user_event(data)
return 'OK', 200
except RateLimitError:
# Tell httpied to retry after 60 seconds
return 'Rate limited', 429, {'Retry-After': '60'}
except Exception as e:
# Temporary error - httpied will retry
logger.error(f"Webhook error: {e}")
return 'Internal error', 500 Set Appropriate Timeouts
Don’t let webhook handlers run forever. Set reasonable timeouts and return quickly:
# When enqueuing, set a realistic timeout client.enqueue(
target="https://api.example.com/webhook",
timeout=10000, # 10 seconds max
body={"data": "value"}
) In your handler, keep processing fast:
@app.route('/webhook', methods=['POST']) def handle_webhook():
data = request.json
# Queue heavy processing for later
background_job.enqueue('process_webhook', data)
# Return immediately
return 'Queued', 200 Monitoring and Debugging
Log Everything
Always log webhook deliveries for debugging:
@app.route('/webhook', methods=['POST']) def handle_webhook():
delivery_id = request.headers.get('X-Httpied-Delivery-Id')
logger.info(f"Received webhook {delivery_id}", extra={
'delivery_id': delivery_id,
'payload': request.json,
'headers': dict(request.headers)
})
# Process... Use httpied Dashboard
The httpied dashboard shows you:
- Full request/response payloads
- Retry attempts and timing
- Failure reasons and stack traces
- Performance metrics
Use it to debug issues before they become critical.
Set Up Alerts
Configure alerts for webhook failures in your dashboard. Get notified when:
- Webhooks fail repeatedly
- Response times exceed thresholds
- Error rates spike
Performance Tips
Batch When Possible
If you’re sending many webhooks, consider batching:
# Instead of many individual webhooks for user in users:
client.enqueue(
target="https://api.example.com/user-update",
body={"user_id": user.id}
)
# Batch them
client.enqueue(
target="https://api.example.com/batch-user-update",
body={"user_ids": [u.id for u in users]}
) Use Payload Compression
For large payloads, enable compression:
client.enqueue( target="https://api.example.com/webhook",
headers={"Content-Encoding": "gzip"},
body=large_payload,
compress=True
) Common Pitfalls
- Not handling retries: Your handler will be called multiple times on failure
- Blocking operations: Don’t make external API calls in webhook handlers
- Missing error handling: Always catch and properly handle exceptions
- Ignoring signatures: Always verify webhook authenticity
- Poor logging: You’ll need logs when debugging production issues
Checklist
Before going to production, verify:
- [ ] Webhook signatures are verified
- [ ] HTTPS is enforced
- [ ] Handlers are idempotent
- [ ] Proper HTTP status codes are returned
- [ ] Timeouts are set appropriately
- [ ] All deliveries are logged
- [ ] Alerts are configured
- [ ] IP allowlisting is enabled (if required)
Conclusion
Following these best practices will help you build secure, reliable webhook handlers that scale with your business. httpied handles the complexity of delivery, retries, and monitoring - you just need to focus on building great handlers.
Questions? We’re here to help! Reach out to our support team anytime.