Webhook Security
Purpose
Securing the Paddle webhook endpoint specifically — this is one of the highest-value attack surfaces in the whole system, since a forged webhook could fake a "sale" or "payout" event.
Non-Negotiable Practices
- Verify Paddle's webhook signature on every single request, using Paddle's signing secret — reject anything that doesn't verify, no exceptions, regardless of how urgent or legitimate a request might look
- Webhook endpoint is not authenticated via Kratos (Paddle isn't a logged-in user) — signature verification is the entire trust mechanism here, which is exactly why it can't be skipped or relaxed
- Idempotent processing (see 02. Event-Driven Architecture) — a replayed or duplicated webhook must never double-credit a wallet or double-trigger a payout
Why This Is Called Out Separately From API Security
Because this endpoint is deliberately unauthenticated (it has to be, since it's called by Paddle, not a logged-in user), it's a fundamentally different threat model than the rest of the API — worth its own explicit doc rather than being buried inside general API Security.
Open Questions
- None blocking — this is a well-established pattern (Paddle's own documentation covers this exact setup); the requirement here is discipline in implementation, not a design decision.
Update (2026-08-23): Paddle → Swich, Plus a Second Webhook Source (Shopify)
Founder decisions, full reasoning in 02. Architecture Decision Log. This doc's every "Paddle" now means Swich, and there's a genuinely new second endpoint with the identical threat model:
- Verify Swich's webhook signature on every single request (
POST /webhooks/swich), using Swich's signing secret — same non-negotiable rule as before, exact signature scheme unconfirmed pending real Swich integration docs. POST /webhooks/shopify-salesneeds the same treatment, independently — verified via Shopify's own webhook HMAC scheme, per-merchant-store secret (05. Payment Flow's per-merchant signed-request note). This is a second unauthenticated, signature-trust-only endpoint this doc's original scope didn't cover — a forged Shopify webhook could fake a sale report just as damagingly as a forged payment webhook could fake a payout.- Idempotent processing (unchanged principle) applies to both endpoints independently — a replayed Swich webhook must never double-credit a wallet; a replayed Shopify webhook must never double-count a sale (already covered by the
external_order_iduniqueness check, 01. Commission Engine). - Title/Purpose framing above ("Securing the Paddle webhook endpoint specifically") should be read as "Securing SellVia's inbound webhook endpoints" now that there are two independent sources, not one.