Webhook Best Practices: Security and Reliability

3 min read
Imanol

Imanol

Author

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

  1. Not handling retries: Your handler will be called multiple times on failure
  2. Blocking operations: Don’t make external API calls in webhook handlers
  3. Missing error handling: Always catch and properly handle exceptions
  4. Ignoring signatures: Always verify webhook authenticity
  5. 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.