zai_payment 1.0.2 โ†’ 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/docs/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Documentation Index
2
+
3
+ Welcome to the Zai Payment Ruby gem documentation. This guide will help you find the information you need.
4
+
5
+ ## ๐Ÿ“– Getting Started
6
+
7
+ **New to the gem?** Start here:
8
+ 1. [Main README](../README.md) - Installation and basic configuration
9
+ 2. [Authentication Guide](AUTHENTICATION.md) - Get tokens with two approaches (short & long way)
10
+ 3. [Webhook Quick Start](WEBHOOK_SECURITY_QUICKSTART.md) - Set up secure webhooks in 5 minutes
11
+ 4. [Webhook Examples](../examples/webhooks.md) - Complete usage examples
12
+
13
+ ## ๐Ÿ—๏ธ Architecture & Design
14
+
15
+ - [**ARCHITECTURE.md**](ARCHITECTURE.md) - System architecture and design principles
16
+ - [**AUTHENTICATION.md**](AUTHENTICATION.md) - OAuth2 implementation, token management, two approaches
17
+ - [**WEBHOOKS.md**](WEBHOOKS.md) - Webhook implementation details, best practices, and patterns
18
+
19
+ ## ๐Ÿ” Security Guides
20
+
21
+ - [**WEBHOOK_SECURITY_QUICKSTART.md**](WEBHOOK_SECURITY_QUICKSTART.md) - Quick 5-minute security setup guide
22
+ - [**WEBHOOK_SIGNATURE.md**](WEBHOOK_SIGNATURE.md) - Detailed signature verification implementation
23
+
24
+ ## ๐Ÿ“ Examples
25
+
26
+ - [**Webhook Examples**](../examples/webhooks.md) - Comprehensive webhook usage examples including:
27
+ - Basic CRUD operations
28
+ - Rails controller implementation
29
+ - Sinatra example
30
+ - Rack middleware
31
+ - Background job processing
32
+ - Idempotency patterns
33
+
34
+ ## ๐Ÿ”— Quick Links
35
+
36
+ ### Authentication
37
+ - **Getting Started**: [Authentication Guide](AUTHENTICATION.md)
38
+ - **Short Way**: `ZaiPayment.token` (one-liner)
39
+ - **Long Way**: `TokenProvider.new(config: config).bearer_token` (full control)
40
+
41
+ ### Webhooks
42
+ - **Setup**: [Quick Start Guide](WEBHOOK_SECURITY_QUICKSTART.md)
43
+ - **Examples**: [Complete Examples](../examples/webhooks.md)
44
+ - **Details**: [Technical Documentation](WEBHOOKS.md)
45
+ - **Security**: [Signature Verification](WEBHOOK_SIGNATURE.md)
46
+
47
+ ### External Resources
48
+ - [Zai Developer Portal](https://developer.hellozai.com/)
49
+ - [Zai API Reference](https://developer.hellozai.com/reference)
50
+ - [Webhook Signature Docs](https://developer.hellozai.com/docs/verify-webhook-signatures)
51
+
52
+ ## ๐Ÿ“š Documentation Structure
53
+
54
+ ```
55
+ docs/
56
+ โ”œโ”€โ”€ README.md # This file - documentation index
57
+ โ”œโ”€โ”€ AUTHENTICATION.md # OAuth2 authentication guide (NEW!)
58
+ โ”œโ”€โ”€ ARCHITECTURE.md # System architecture
59
+ โ”œโ”€โ”€ WEBHOOKS.md # Webhook technical docs
60
+ โ”œโ”€โ”€ WEBHOOK_SECURITY_QUICKSTART.md # Quick security setup
61
+ โ””โ”€โ”€ WEBHOOK_SIGNATURE.md # Signature implementation
62
+
63
+ examples/
64
+ โ””โ”€โ”€ webhooks.md # Complete webhook examples
65
+ ```
66
+
67
+ ## ๐Ÿ’ก Tips
68
+
69
+ - **Getting tokens?** Check [AUTHENTICATION.md](AUTHENTICATION.md) for both approaches
70
+ - **Looking for code examples?** Check [examples/webhooks.md](../examples/webhooks.md)
71
+ - **Need quick setup?** See [WEBHOOK_SECURITY_QUICKSTART.md](WEBHOOK_SECURITY_QUICKSTART.md)
72
+ - **Want to understand the design?** Read [ARCHITECTURE.md](ARCHITECTURE.md)
73
+ - **Security details?** Review [WEBHOOK_SIGNATURE.md](WEBHOOK_SIGNATURE.md)
74
+
75
+ ## ๐Ÿ†˜ Need Help?
76
+
77
+ 1. Check the relevant documentation section above
78
+ 2. Review the [examples](../examples/webhooks.md)
79
+ 3. Consult the [Zai API documentation](https://developer.hellozai.com/)
80
+ 4. Open an issue on GitHub
81
+
data/docs/WEBHOOKS.md ADDED
@@ -0,0 +1,417 @@
1
+ # Zai Payment Webhook Implementation
2
+
3
+ ## Overview
4
+ This document provides a summary of the webhook implementation in the zai_payment gem.
5
+
6
+ ## Architecture
7
+
8
+ ### Core Components
9
+
10
+ 1. **Client** (`lib/zai_payment/client.rb`)
11
+ - Base HTTP client for making API requests
12
+ - Handles authentication automatically via TokenProvider
13
+ - Supports GET, POST, PATCH, DELETE methods
14
+ - Manages connection with proper headers and JSON encoding/decoding
15
+
16
+ 2. **Response** (`lib/zai_payment/response.rb`)
17
+ - Wraps Faraday responses
18
+ - Provides convenient methods: `success?`, `client_error?`, `server_error?`
19
+ - Automatically raises appropriate errors based on HTTP status
20
+ - Extracts data and metadata from response body
21
+
22
+ 3. **Webhook Resource** (`lib/zai_payment/resources/webhook.rb`)
23
+ - Implements all CRUD operations for webhooks
24
+ - Full input validation
25
+ - Clean, documented API
26
+
27
+ 4. **Enhanced Error Handling** (`lib/zai_payment/errors.rb`)
28
+ - Specific error classes for different scenarios
29
+ - Makes debugging and error handling easier
30
+
31
+ ## API Methods
32
+
33
+ ### List Webhooks
34
+ ```ruby
35
+ ZaiPayment.webhooks.list(limit: 10, offset: 0)
36
+ ```
37
+ - Returns paginated list of webhooks
38
+ - Response includes `data` (array of webhooks) and `meta` (pagination info)
39
+
40
+ ### Show Webhook
41
+ ```ruby
42
+ ZaiPayment.webhooks.show(webhook_id)
43
+ ```
44
+ - Returns details of a specific webhook
45
+ - Raises `NotFoundError` if webhook doesn't exist
46
+
47
+ ### Create Webhook
48
+ ```ruby
49
+ ZaiPayment.webhooks.create(
50
+ url: 'https://example.com/webhook',
51
+ object_type: 'transactions',
52
+ enabled: true,
53
+ description: 'Optional description'
54
+ )
55
+ ```
56
+ - Validates URL format
57
+ - Validates required fields
58
+ - Returns created webhook with ID
59
+
60
+ ### Update Webhook
61
+ ```ruby
62
+ ZaiPayment.webhooks.update(
63
+ webhook_id,
64
+ url: 'https://example.com/new-webhook',
65
+ enabled: false
66
+ )
67
+ ```
68
+ - All fields are optional
69
+ - Only updates provided fields
70
+ - Validates URL format if URL is provided
71
+
72
+ ### Delete Webhook
73
+ ```ruby
74
+ ZaiPayment.webhooks.delete(webhook_id)
75
+ ```
76
+ - Permanently deletes the webhook
77
+ - Returns 204 No Content on success
78
+
79
+ ## Error Handling
80
+
81
+ The gem provides specific error classes:
82
+
83
+ | Error Class | HTTP Status | Description |
84
+ |------------|-------------|-------------|
85
+ | `ValidationError` | 400, 422 | Invalid input data |
86
+ | `UnauthorizedError` | 401 | Authentication failed |
87
+ | `ForbiddenError` | 403 | Access denied |
88
+ | `NotFoundError` | 404 | Resource not found |
89
+ | `RateLimitError` | 429 | Too many requests |
90
+ | `ServerError` | 5xx | Server-side error |
91
+ | `TimeoutError` | - | Request timeout |
92
+ | `ConnectionError` | - | Connection failed |
93
+
94
+ Example:
95
+ ```ruby
96
+ begin
97
+ response = ZaiPayment.webhooks.create(...)
98
+ rescue ZaiPayment::Errors::ValidationError => e
99
+ puts "Validation failed: #{e.message}"
100
+ rescue ZaiPayment::Errors::UnauthorizedError => e
101
+ puts "Authentication failed: #{e.message}"
102
+ end
103
+ ```
104
+
105
+ ## Best Practices Implemented
106
+
107
+ 1. **Single Responsibility**: Each class has a clear, focused purpose
108
+ 2. **DRY (Don't Repeat Yourself)**: Client and Response classes are reusable
109
+ 3. **Error Handling**: Comprehensive error handling with specific error classes
110
+ 4. **Input Validation**: All inputs are validated before making API calls
111
+ 5. **Documentation**: Inline documentation with examples
112
+ 6. **Testing**: Comprehensive test coverage using RSpec
113
+ 7. **Thread Safety**: TokenProvider uses mutex for thread-safe token refresh
114
+ 8. **Configuration**: Centralized configuration management
115
+ 9. **RESTful Design**: Follows REST principles for resource management
116
+ 10. **Response Wrapping**: Consistent response format across all methods
117
+
118
+ ## Usage Examples
119
+
120
+ See `examples/webhooks.rb` for complete examples including:
121
+ - Basic CRUD operations
122
+ - Pagination
123
+ - Error handling
124
+ - Custom client instances
125
+
126
+ ## Testing
127
+
128
+ Run the webhook tests:
129
+ ```bash
130
+ bundle exec rspec spec/zai_payment/resources/webhook_spec.rb
131
+ ```
132
+
133
+ The test suite covers:
134
+ - All CRUD operations
135
+ - Success and error scenarios
136
+ - Input validation
137
+ - Error handling
138
+ - Edge cases
139
+
140
+ ## Future Enhancements
141
+
142
+ Potential improvements for future versions:
143
+ 1. Webhook job management (list jobs, show job details)
144
+ 2. ~~Webhook signature verification~~ โœ… **Implemented**
145
+ 3. Webhook retry logic
146
+ 4. Bulk operations
147
+ 5. Async webhook operations
148
+
149
+ ## Webhook Security: Signature Verification
150
+
151
+ ### Overview
152
+
153
+ Webhook signature verification ensures that webhook requests truly come from Zai and haven't been tampered with. This protection guards against:
154
+ - **Man-in-the-middle attacks**: Verify the sender is Zai
155
+ - **Replay attacks**: Timestamp verification prevents old webhooks from being reused
156
+ - **Data tampering**: HMAC ensures the payload hasn't been modified
157
+
158
+ ### Setup
159
+
160
+ #### Step 1: Generate and Store a Secret Key
161
+
162
+ First, create a secret key that will be shared between you and Zai:
163
+
164
+ ```ruby
165
+ require 'securerandom'
166
+
167
+ # Generate a cryptographically secure secret key (at least 32 bytes)
168
+ secret_key = SecureRandom.alphanumeric(32)
169
+
170
+ # Store this securely in your environment variables
171
+ # DO NOT commit this to version control!
172
+ ENV['ZAI_WEBHOOK_SECRET'] = secret_key
173
+
174
+ # Register the secret key with Zai
175
+ response = ZaiPayment.webhooks.create_secret_key(secret_key: secret_key)
176
+
177
+ if response.success?
178
+ puts "Secret key registered successfully!"
179
+ end
180
+ ```
181
+
182
+ **Important Security Notes:**
183
+ - Store the secret key in environment variables or a secure vault (e.g., AWS Secrets Manager, HashiCorp Vault)
184
+ - Never commit the secret key to version control
185
+ - Rotate the secret key periodically
186
+ - Use at least 32 bytes for the secret key
187
+
188
+ #### Step 2: Verify Webhook Signatures
189
+
190
+ In your webhook endpoint, verify each incoming request:
191
+
192
+ ```ruby
193
+ # Rails example
194
+ class WebhooksController < ApplicationController
195
+ skip_before_action :verify_authenticity_token
196
+
197
+ def zai_webhook
198
+ payload = request.body.read
199
+ signature_header = request.headers['Webhooks-signature']
200
+ secret_key = ENV['ZAI_WEBHOOK_SECRET']
201
+
202
+ begin
203
+ # Verify the signature
204
+ if ZaiPayment.webhooks.verify_signature(
205
+ payload: payload,
206
+ signature_header: signature_header,
207
+ secret_key: secret_key,
208
+ tolerance: 300 # 5 minutes
209
+ )
210
+ # Signature is valid, process the webhook
211
+ webhook_data = JSON.parse(payload)
212
+ process_webhook(webhook_data)
213
+
214
+ render json: { status: 'success' }, status: :ok
215
+ else
216
+ # Invalid signature
217
+ render json: { error: 'Invalid signature' }, status: :unauthorized
218
+ end
219
+ rescue ZaiPayment::Errors::ValidationError => e
220
+ # Signature verification failed (e.g., timestamp too old)
221
+ Rails.logger.error "Webhook signature verification failed: #{e.message}"
222
+ render json: { error: e.message }, status: :unauthorized
223
+ end
224
+ end
225
+
226
+ private
227
+
228
+ def process_webhook(data)
229
+ # Your webhook processing logic here
230
+ Rails.logger.info "Processing webhook: #{data['event']}"
231
+ end
232
+ end
233
+ ```
234
+
235
+ ### How It Works
236
+
237
+ The verification process follows these steps:
238
+
239
+ 1. **Extract Components**: Parse the `Webhooks-signature` header to get timestamp and signature(s)
240
+ - Header format: `t=1257894000,v=signature1,v=signature2`
241
+
242
+ 2. **Verify Timestamp**: Check that the webhook isn't too old (prevents replay attacks)
243
+ - Default tolerance: 300 seconds (5 minutes)
244
+ - Configurable via the `tolerance` parameter
245
+
246
+ 3. **Generate Expected Signature**: Create HMAC SHA256 signature
247
+ - Signed payload: `timestamp.request_body`
248
+ - Uses base64url encoding (URL-safe, no padding)
249
+
250
+ 4. **Compare Signatures**: Use constant-time comparison to prevent timing attacks
251
+ - Returns `true` if any signature in the header matches
252
+
253
+ ### Advanced Examples
254
+
255
+ #### Custom Tolerance Window
256
+
257
+ ```ruby
258
+ # Allow webhooks up to 10 minutes old
259
+ ZaiPayment.webhooks.verify_signature(
260
+ payload: payload,
261
+ signature_header: signature_header,
262
+ secret_key: secret_key,
263
+ tolerance: 600 # 10 minutes
264
+ )
265
+ ```
266
+
267
+ #### Generate Signatures for Testing
268
+
269
+ ```ruby
270
+ # Generate a signature for testing your webhook endpoint
271
+ payload = '{"event": "transaction.updated", "id": "txn_123"}'
272
+ secret_key = ENV['ZAI_WEBHOOK_SECRET']
273
+ timestamp = Time.now.to_i
274
+
275
+ signature = ZaiPayment.webhooks.generate_signature(payload, secret_key, timestamp)
276
+ signature_header = "t=#{timestamp},v=#{signature}"
277
+
278
+ # Now use this in your test request
279
+ # This is useful for integration tests
280
+ ```
281
+
282
+ #### Handling Multiple Signatures
283
+
284
+ Zai may include multiple signatures in the header (e.g., during key rotation):
285
+
286
+ ```ruby
287
+ # The verify_signature method automatically handles multiple signatures
288
+ # It returns true if ANY signature matches
289
+ signature_header = "t=1257894000,v=old_sig,v=new_sig"
290
+ result = ZaiPayment.webhooks.verify_signature(
291
+ payload: payload,
292
+ signature_header: signature_header,
293
+ secret_key: secret_key
294
+ )
295
+ ```
296
+
297
+ ### Testing Your Implementation
298
+
299
+ Create a test to ensure your webhook endpoint properly validates signatures:
300
+
301
+ ```ruby
302
+ require 'rails_helper'
303
+
304
+ RSpec.describe WebhooksController, type: :controller do
305
+ let(:secret_key) { SecureRandom.alphanumeric(32) }
306
+ let(:payload) { { event: 'transaction.updated', id: 'txn_123' }.to_json }
307
+ let(:timestamp) { Time.now.to_i }
308
+
309
+ before do
310
+ ENV['ZAI_WEBHOOK_SECRET'] = secret_key
311
+ end
312
+
313
+ describe 'POST #zai_webhook' do
314
+ context 'with valid signature' do
315
+ it 'processes the webhook' do
316
+ signature = ZaiPayment::Resources::Webhook.new.generate_signature(
317
+ payload, secret_key, timestamp
318
+ )
319
+
320
+ request.headers['Webhooks-signature'] = "t=#{timestamp},v=#{signature}"
321
+ post :zai_webhook, body: payload
322
+
323
+ expect(response).to have_http_status(:ok)
324
+ end
325
+ end
326
+
327
+ context 'with invalid signature' do
328
+ it 'rejects the webhook' do
329
+ request.headers['Webhooks-signature'] = "t=#{timestamp},v=invalid_sig"
330
+ post :zai_webhook, body: payload
331
+
332
+ expect(response).to have_http_status(:unauthorized)
333
+ end
334
+ end
335
+ end
336
+ end
337
+ ```
338
+
339
+ ### Troubleshooting
340
+
341
+ #### Common Issues
342
+
343
+ 1. **"Invalid signature header: missing or invalid timestamp"**
344
+ - Ensure the header format is correct: `t=timestamp,v=signature`
345
+ - Check that timestamp is a valid Unix timestamp
346
+
347
+ 2. **"Webhook timestamp is outside tolerance"**
348
+ - Check your server's clock synchronization (use NTP)
349
+ - Increase the tolerance if network latency is high
350
+ - Log the timestamp difference to diagnose timing issues
351
+
352
+ 3. **Signature doesn't match**
353
+ - Verify you're using the raw request body (not parsed JSON)
354
+ - Ensure the secret key matches what you registered with Zai
355
+ - Check for any character encoding issues
356
+
357
+ #### Debugging Tips
358
+
359
+ ```ruby
360
+ # Enable detailed logging for debugging
361
+ def verify_webhook_with_logging(payload, signature_header, secret_key)
362
+ webhook = ZaiPayment::Resources::Webhook.new
363
+
364
+ begin
365
+ # Extract timestamp and signature
366
+ timestamp = signature_header.match(/t=(\d+)/)[1].to_i
367
+ signature = signature_header.match(/v=([^,]+)/)[1]
368
+
369
+ # Log details
370
+ Rails.logger.debug "Webhook timestamp: #{timestamp}"
371
+ Rails.logger.debug "Current time: #{Time.now.to_i}"
372
+ Rails.logger.debug "Time difference: #{Time.now.to_i - timestamp}s"
373
+ Rails.logger.debug "Payload length: #{payload.bytesize} bytes"
374
+
375
+ # Generate expected signature for comparison
376
+ expected = webhook.generate_signature(payload, secret_key, timestamp)
377
+ Rails.logger.debug "Expected signature: #{expected[0..10]}..."
378
+ Rails.logger.debug "Received signature: #{signature[0..10]}..."
379
+
380
+ # Verify
381
+ webhook.verify_signature(
382
+ payload: payload,
383
+ signature_header: signature_header,
384
+ secret_key: secret_key
385
+ )
386
+ rescue => e
387
+ Rails.logger.error "Verification failed: #{e.message}"
388
+ false
389
+ end
390
+ end
391
+ ```
392
+
393
+ ### Security Best Practices
394
+
395
+ 1. **Always Verify Signatures**: Never process webhooks without verification in production
396
+ 2. **Use HTTPS**: Ensure your webhook endpoint uses HTTPS
397
+ 3. **Implement Rate Limiting**: Protect against DoS attacks
398
+ 4. **Log Failed Attempts**: Monitor for suspicious activity
399
+ 5. **Rotate Secrets**: Periodically update your secret key
400
+ 6. **Use Environment Variables**: Never hardcode secret keys
401
+ 7. **Validate Payload**: After verifying the signature, validate the payload structure
402
+ 8. **Idempotency**: Design webhook handlers to be idempotent (safe to replay)
403
+
404
+ ### References
405
+
406
+ - [Zai Webhook Signature Documentation](https://developer.hellozai.com/docs/verify-webhook-signatures)
407
+ - [Create Secret Key API](https://developer.hellozai.com/reference/createsecretkey)
408
+
409
+ ## API Reference
410
+
411
+ For the official Zai API documentation, see:
412
+ - [List Webhooks](https://developer.hellozai.com/reference/getallwebhooks)
413
+ - [Show Webhook](https://developer.hellozai.com/reference/getwebhookbyid)
414
+ - [Create Webhook](https://developer.hellozai.com/reference/createwebhook)
415
+ - [Update Webhook](https://developer.hellozai.com/reference/updatewebhook)
416
+ - [Delete Webhook](https://developer.hellozai.com/reference/deletewebhookbyid)
417
+
@@ -0,0 +1,141 @@
1
+ # Quick Start: Webhook Security
2
+
3
+ ## ๐Ÿš€ 5-Minute Setup
4
+
5
+ ### Step 1: Generate & Register Secret Key (One Time)
6
+
7
+ ```ruby
8
+ require 'securerandom'
9
+
10
+ secret_key = SecureRandom.alphanumeric(32)
11
+ ZaiPayment.webhooks.create_secret_key(secret_key: secret_key)
12
+
13
+ # Save to your .env file
14
+ # ZAI_WEBHOOK_SECRET=your_generated_secret_key
15
+ ```
16
+
17
+ ### Step 2: Add Verification to Your Webhook Endpoint
18
+
19
+ ```ruby
20
+ # app/controllers/webhooks_controller.rb
21
+ class WebhooksController < ApplicationController
22
+ skip_before_action :verify_authenticity_token
23
+
24
+ def zai_webhook
25
+ payload = request.body.read
26
+ signature = request.headers['Webhooks-signature']
27
+
28
+ # โœ… Verify signature
29
+ unless verify_webhook(payload, signature)
30
+ return render json: { error: 'Invalid signature' }, status: :unauthorized
31
+ end
32
+
33
+ # ๐ŸŽ‰ Process your webhook
34
+ webhook_data = JSON.parse(payload)
35
+ handle_webhook(webhook_data)
36
+
37
+ render json: { status: 'success' }
38
+ end
39
+
40
+ private
41
+
42
+ def verify_webhook(payload, signature)
43
+ ZaiPayment.webhooks.verify_signature(
44
+ payload: payload,
45
+ signature_header: signature,
46
+ secret_key: ENV['ZAI_WEBHOOK_SECRET'],
47
+ tolerance: 300 # 5 minutes
48
+ )
49
+ rescue ZaiPayment::Errors::ValidationError
50
+ false
51
+ end
52
+
53
+ def handle_webhook(data)
54
+ case data['event']
55
+ when 'transaction.created'
56
+ # Your logic here
57
+ when 'transaction.completed'
58
+ # Your logic here
59
+ end
60
+ end
61
+ end
62
+ ```
63
+
64
+ ### Step 3: Add Route
65
+
66
+ ```ruby
67
+ # config/routes.rb
68
+ post '/webhooks/zai', to: 'webhooks#zai_webhook'
69
+ ```
70
+
71
+ ## ๐Ÿงช Testing
72
+
73
+ ```ruby
74
+ # spec/controllers/webhooks_controller_spec.rb
75
+ RSpec.describe WebhooksController do
76
+ let(:secret_key) { ENV['ZAI_WEBHOOK_SECRET'] }
77
+ let(:payload) { { event: 'transaction.updated' }.to_json }
78
+
79
+ it 'accepts valid webhooks' do
80
+ timestamp = Time.now.to_i
81
+ signature = ZaiPayment::Resources::Webhook.new.generate_signature(
82
+ payload, secret_key, timestamp
83
+ )
84
+
85
+ request.headers['Webhooks-signature'] = "t=#{timestamp},v=#{signature}"
86
+ post :zai_webhook, body: payload
87
+
88
+ expect(response).to have_http_status(:ok)
89
+ end
90
+
91
+ it 'rejects invalid signatures' do
92
+ request.headers['Webhooks-signature'] = "t=#{Time.now.to_i},v=bad_signature"
93
+ post :zai_webhook, body: payload
94
+
95
+ expect(response).to have_http_status(:unauthorized)
96
+ end
97
+ end
98
+ ```
99
+
100
+ ## ๐Ÿ” Security Checklist
101
+
102
+ - โœ… Secret key is at least 32 bytes
103
+ - โœ… Secret key stored in environment variables (not in code)
104
+ - โœ… Using HTTPS for webhook endpoint
105
+ - โœ… Signature verification before processing
106
+ - โœ… Timestamp tolerance configured appropriately
107
+ - โœ… Error logging for failed verifications
108
+ - โœ… Tests cover both valid and invalid scenarios
109
+
110
+ ## ๐Ÿ› Common Issues
111
+
112
+ ### "Invalid signature header: missing or invalid timestamp"
113
+ **Fix**: Ensure header format is `t=timestamp,v=signature`
114
+
115
+ ### "Webhook timestamp is outside tolerance"
116
+ **Fix**: Check server clock synchronization or increase tolerance
117
+
118
+ ### Signature doesn't match
119
+ **Fix**:
120
+ - Use raw request body (don't parse it first)
121
+ - Verify secret key matches what was registered
122
+ - Check for encoding issues
123
+
124
+ ## ๐Ÿ“š Full Documentation
125
+
126
+ - [Complete Setup Guide](docs/WEBHOOKS.md#webhook-security-signature-verification)
127
+ - [More Examples](examples/webhooks.md#webhook-security-complete-setup-guide)
128
+ - [Zai Official Docs](https://developer.hellozai.com/docs/verify-webhook-signatures)
129
+
130
+ ## ๐Ÿ’ก Pro Tips
131
+
132
+ 1. **Use Background Jobs**: Process webhooks asynchronously for better performance
133
+ 2. **Implement Idempotency**: Check if webhook was already processed
134
+ 3. **Add Rate Limiting**: Protect against DoS attacks
135
+ 4. **Log Everything**: Monitor for suspicious activity
136
+ 5. **Test Replay Attacks**: Ensure old webhooks are rejected
137
+
138
+ ---
139
+
140
+ **Need Help?** See the [full implementation guide](WEBHOOK_SIGNATURE_IMPLEMENTATION.md)
141
+