Webhook Signature Verification Guide#
Overview#
KutanaPay signs all webhook requests with HMAC-SHA256 signatures to ensure authenticity and prevent tampering. You should always verify webhook signatures before processing webhook data.How It Works#
1.
KutanaPay generates a signature using your webhook's secret key
2.
The signature is sent in the X-Webhook-Signature header
3.
Your endpoint verifies the signature matches the payload
4.
If verification fails, reject the webhook
Every webhook request includes these headers:X-Webhook-Signature: sha256=<signature>
X-Webhook-Event: checkout.created
X-Webhook-Idempotency-Key: <uuid>
User-Agent: KutanaPay-Webhook/1.0
Content-Type: application/json
Payload Structure#
All webhooks follow this versioned structure:{
"version": "v1",
"event_type": "checkout.created",
"timestamp": "2025-11-17T12:34:56Z",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
"merchant_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"data": {
}
}
Signature Verification#
Python#
Node.js#
Security Best Practices#
1. Always Verify Signatures#
2. Use Constant-Time Comparison#
Prevents timing attacks. Always use:Python: hmac.compare_digest()
Node.js: crypto.timingSafeEqual()
Ruby: ActiveSupport::SecurityUtils.secure_compare()
3. Validate Timestamp#
Reject old webhooks to prevent replay attacks:4. Use Idempotency Keys#
Store processed idempotency_key values to prevent duplicate processing:5. Return 2xx Quickly#
Respond within your configured timeout (default 30s):6. Handle Retries Gracefully#
KutanaPay retries failed webhooks (5min → 30min → 6hr):Make operations idempotent
Return 2xx for already-processed webhooks
Return 4xx for permanent failures (won't retry)
Return 5xx for temporary failures (will retry)
Event Types#
| Event Type | Description | When Triggered |
|---|
checkout.created | New checkout session created | Customer initiates payment |
checkout.paid | Customer marked checkout as paid | Customer confirms payment |
checkout.approved | Admin approved payment | Admin verifies payment |
checkout.rejected | Admin rejected payment | Admin rejects payment |
checkout.expired | Checkout expired without payment | Timeout (24h default) |
checkout.completed | Payment fully processed | Funds credited |
checkout.failed | Payment processing failed | Payment error |
deposit.received | Funds deposited to wallet | Admin deposit |
payout.processed | Payout to third party completed | Payout sent |
withdrawal.completed | Owner withdrawal processed | Owner withdraws funds |
settlement.completed | T+1 settlement batch processed | Daily settlement |
user.invited | Team member invited | User invite sent |
user.joined | Team member accepted invite | User joined team |
Testing Webhooks#
Use the test endpoint in your dashboard:Troubleshooting#
Signature Verification Fails#
1.
Check raw body: Don't parse JSON before verifying 2.
Check secret: Ensure you're using the correct secret from dashboard
3.
Check encoding: Use UTF-8 encoding consistently
4.
Check header name: Case-sensitive: X-Webhook-Signature
Webhooks Not Received#
1.
Check URL accessibility: Ensure your endpoint is publicly accessible
2.
Check HTTPS: Production requires HTTPS
3.
Check firewall: Allow KutanaPay IPs (contact support for list)
4.
Check logs: View delivery attempts in dashboard
Auto-Disabled After Failures#
Your webhook was disabled after 10 consecutive failures:1.
View logs: Check last 10 delivery attempts
2.
Test endpoint: Use test endpoint to diagnose
3.
Fix issues: Address errors in your endpoint
4.
Re-enable: Reactivate webhook in dashboard
Rate Limits#
Delivery: 100 requests/minute per merchant
Config changes: 10 changes/hour per merchant
Modified at 2025-11-19 14:59:22