Overview
Payment processor webhooks are the primary mechanism for asynchronous event delivery -- charge confirmations, payout status updates, refund completions, and dispute notifications. Because these endpoints are publicly accessible and directly affect financial records, CommunityPay applies layered security controls at every webhook entry point.
Rate Limiting
The WebhookRateLimitMiddleware applies per-provider, per-IP rate limits to all webhook endpoints:
| Provider | Rate Limit | Window |
|---|---|---|
| Stripe | 100 requests/minute | 60 seconds |
| Dwolla | 60 requests/minute | 60 seconds |
| Plaid | 50 requests/minute | 60 seconds |
| Unknown | 30 requests/minute | 60 seconds |
The middleware:
1. Detects webhook endpoints by URL path matching (/webhook in path)
2. Extracts the provider name from the URL structure
3. Constructs a per-provider, per-IP cache key
4. Uses Redis-backed sliding window counters
5. Returns HTTP 429 (Too Many Requests) when limits are exceeded
Rate limit violations are logged with the offending IP address for security monitoring.
Signature Verification
The WebhookSignatureVerifier implements provider-specific signature verification:
Stripe Verification
Stripe webhooks use a composite signature header (Stripe-Signature) containing a timestamp and one or more HMAC-SHA256 signatures:
- Parse the signature header to extract timestamp (
t=) and signature values (v1=) - Validate timestamp: Reject if the webhook is older than 300 seconds (5-minute replay window)
- Compute expected signature: HMAC-SHA256 of
{timestamp}.{payload}using the webhook signing secret - Compare: Use
hmac.compare_digestfor constant-time comparison, preventing timing-based attacks
Dwolla Verification
Dwolla uses a simpler HMAC-SHA256 signature:
1. Compute HMAC-SHA256 of the raw payload using the webhook secret
2. Compare against the X-Request-Signature-SHA-256 header using hmac.compare_digest
Plaid Verification
Plaid webhooks use JWT-based verification with the Plaid-Verification header.
Payload Size Limits
The @webhook_size_limit decorator enforces maximum payload sizes:
- Default maximum: 512 KB
- Checks the
Content-Lengthheader before processing - Returns HTTP 400 (Bad Request) for oversized payloads
- Prevents memory exhaustion attacks on webhook endpoints
Replay Attack Prevention
The @verify_webhook_timestamp decorator provides an additional layer of replay protection:
- Configurable maximum age (default: 300 seconds)
- Rejects webhooks with timestamps outside the acceptable window
- Works in conjunction with provider-specific timestamp validation
For Stripe specifically, timestamp validation is built into the signature verification itself -- the signed payload includes the timestamp, making it impossible to replay a webhook with a forged timestamp.
Decorator Composition
Webhook endpoints compose these security controls:
@require_webhook_signature('stripe') # Verify signature present
@webhook_size_limit(max_size_kb=512) # Enforce payload size
@verify_webhook_timestamp(max_age=300) # Prevent replay
def stripe_webhook(request):
# Process verified webhook
...
Each decorator operates independently, and any failure short-circuits the request before business logic executes.
Security Properties
| Property | Mechanism |
|---|---|
| Authentication | HMAC-SHA256 signature per provider |
| Integrity | Signature covers full payload |
| Freshness | Timestamp validation (300s window) |
| Availability | Per-provider rate limiting |
| Size bounds | Payload size decorator |
| Timing safety | hmac.compare_digest constant-time comparison |
How CommunityPay Enforces This
- Per-provider rate limits enforced via middleware (Stripe: 100/min, Dwolla: 60/min, Plaid: 50/min)
- HMAC-SHA256 signature verification with constant-time comparison (hmac.compare_digest)
- Replay attack prevention via 300-second timestamp validation window
- Payload size limits enforced via decorator (default: 512 KB maximum)