require 'securerandom'
secret_key = SecureRandom.alphanumeric(32)
ZaiPayment.webhooks.create_secret_key(secret_key: secret_key)
# Save to your .env file
# ZAI_WEBHOOK_SECRET=your_generated_secret_key# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def zai_webhook
payload = request.body.read
signature = request.headers['Webhooks-signature']
# ✅ Verify signature
unless verify_webhook(payload, signature)
return render json: { error: 'Invalid signature' }, status: :unauthorized
end
# 🎉 Process your webhook
webhook_data = JSON.parse(payload)
handle_webhook(webhook_data)
render json: { status: 'success' }
end
private
def verify_webhook(payload, signature)
ZaiPayment.webhooks.verify_signature(
payload: payload,
signature_header: signature,
secret_key: ENV['ZAI_WEBHOOK_SECRET'],
tolerance: 300 # 5 minutes
)
rescue ZaiPayment::Errors::ValidationError
false
end
def handle_webhook(data)
case data['event']
when 'transaction.created'
# Your logic here
when 'transaction.completed'
# Your logic here
end
end
end# config/routes.rb
post '/webhooks/zai', to: 'webhooks#zai_webhook'# spec/controllers/webhooks_controller_spec.rb
RSpec.describe WebhooksController do
let(:secret_key) { ENV['ZAI_WEBHOOK_SECRET'] }
let(:payload) { { event: 'transaction.updated' }.to_json }
it 'accepts valid webhooks' do
timestamp = Time.now.to_i
signature = ZaiPayment::Resources::Webhook.new.generate_signature(
payload, secret_key, timestamp
)
request.headers['Webhooks-signature'] = "t=#{timestamp},v=#{signature}"
post :zai_webhook, body: payload
expect(response).to have_http_status(:ok)
end
it 'rejects invalid signatures' do
request.headers['Webhooks-signature'] = "t=#{Time.now.to_i},v=bad_signature"
post :zai_webhook, body: payload
expect(response).to have_http_status(:unauthorized)
end
end- ✅ Secret key is at least 32 bytes
- ✅ Secret key stored in environment variables (not in code)
- ✅ Using HTTPS for webhook endpoint
- ✅ Signature verification before processing
- ✅ Timestamp tolerance configured appropriately
- ✅ Error logging for failed verifications
- ✅ Tests cover both valid and invalid scenarios
Fix: Ensure header format is t=timestamp,v=signature
Fix: Check server clock synchronization or increase tolerance
Fix:
- Use raw request body (don't parse it first)
- Verify secret key matches what was registered
- Check for encoding issues
- Use Background Jobs: Process webhooks asynchronously for better performance
- Implement Idempotency: Check if webhook was already processed
- Add Rate Limiting: Protect against DoS attacks
- Log Everything: Monitor for suspicious activity
- Test Replay Attacks: Ensure old webhooks are rejected