Process signed webhooks
Fluent verifies a delivery, updates local snapshots and dispatches events through
your storage driver. Your framework route supplies the raw bytes and signature.
Use the vatly instance wired in Configuration.
from vatly_fluent import Vatly
async def example(vatly: Vatly, raw_body: bytes, signature: str):
processed = await vatly.webhook_processor().handle(raw_body, signature)
return "processed" if processed else "duplicate"
Respond 2xx after commit, including for a committed duplicate. Invalid signatures
raise VatlyInvalidSignatureError; return 400. Let processing/storage failures
return 5xx so Vatly retries. Do not parse and re-encode the body before verification.
Make delivery atomic
| Driver responsibility | Required behavior |
|---|---|
webhook_calls.process(event, work) | Claim signed event.id; run work; commit all writes together |
| Repositories and checkout links | Participate in that same transaction |
events.dispatch(event) | Write an outbox entry, not a network side effect |
| Failure or concurrent duplicate | Roll back on failure; enforce DB uniqueness/locking |
The signature's default clock tolerance is 300 seconds; secret sequences support rotation. Deduplicate the signed ID, not an unsigned event-ID header. Scope all claims by tenant/storefront/mode. Unknown signed events remain retained/dispatched.
Know what the built-in reactions do
| Event | Local effect |
|---|---|
subscription.started | Upsert; first save emits local.subscription_created |
| Subscription update/billing/resume/cancel events | Update an existing snapshot and lifecycle expiry |
order.paid, order.payment_failed | Upsert order snapshot; first paid save emits local.order_created |
order.canceled | Update an existing order |
| Refund/chargeback events | Upsert optional reversal repositories |
checkout.paid | Link through a host-owned checkout record |
| Other/unknown events | Dispatch without an invented resource write |
Reactions make no implicit API reads. Customer bindings never determine ownership. Created events can precede eventual ownership linkage; outbox consumers must read current persisted ownership before acting. Dispatch external effects after commit with idempotent consumer keys. Deduplication is not event ordering: reconcile stale or conflicting snapshots explicitly. See subscription recovery.