Billing providers retry webhooks when delivery fails, and the same event can arrive more than once. A SaaS webhook must verify authenticity, record the event atomically, return quickly, and make later processing idempotent.
Last updated: September 8, 2026.
CREATE TABLE billing_events (
provider_event_id VARCHAR(100) PRIMARY KEY,
event_type VARCHAR(100) NOT NULL,
object_id VARCHAR(100) NOT NULL,
payload JSON NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);The primary key rejects a duplicate provider event ID. Treat that duplicate as an already accepted delivery and return a successful HTTP response.
Keep the HTTP handler small
- Read the raw request body.
- Verify the provider’s signature and timestamp before parsing business fields.
- Accept only event types your application uses.
- Insert the event and enqueue its ID in one reliable workflow.
- Return a success response before performing slow account updates.
The worker loads the stored event, applies the desired subscription state, records completion, and can run again without issuing duplicate credits, emails, or entitlements. The same pattern belongs in other SaaS background jobs.
Do not assume delivery order
An older event may arrive after a newer one. When possible, retrieve the current subscription from the provider or compare provider timestamps and versions before changing local state. Store the provider customer and subscription IDs on the tenant created by the onboarding workflow.
Separate billing state from permissions
Translate provider status into a small internal entitlement model. For example, a past-due subscription might keep read access during a grace period while disabling new resource creation. Record every automatic entitlement change in the SaaS audit log.
Monitor webhook age, failure count, and queue delay so billing problems become visible before customers report them. Stripe’s official webhook guidance recommends logging processed event IDs, listening only to required event types, and moving event handling to an asynchronous queue.