Reliable Webhooks & Background Jobs
How to design reliable background jobs, provider webhooks, idempotent writes, retries, alerting, and recovery with Django or Node.js.
How do you stop background jobs and webhooks from losing work?
First make the business state durable, then make external work repeatable. Persist an operation ID, validate signed events where supported, process callbacks idempotently, retry transient failures with limits, and reconcile missing or delayed provider updates.
1. Define the transaction that must never be ambiguous
Start with the user's business outcome, such as creating a booking, recording a membership or confirming that a notification is due. Which database record constitutes the truth? Which operations can be delayed? If an HTTP request ends before the follow-up action succeeds, the application still needs to know what remains unfinished. A database transaction can protect local changes, but it cannot atomically commit an independent email, queue or payment provider.
Implementation checklist
- Write down each permitted business state
- Define one stable identifier per operation
- Separate user-facing success from external side effects
2. Understand the two dangerous failure windows
An application may commit to its database but crash before publishing a message. Or a provider may complete a request while the client times out waiting for acknowledgment. Retrying blindly can lose work in the first case or duplicate it in the second. A persistent outbox record, or another explicitly durable work queue, gives the system something to replay. It does not eliminate the need to detect repeated provider responses.
Implementation checklist
- Simulate commit succeeded, message publish failed
- Simulate provider success followed by timeout
- Decide what the user should see while work remains pending
3. Implement idempotent handlers and honest acknowledgments
Use request or event IDs that remain stable across retries. Store which operations have already been applied, check them within the same transaction that updates business state, and return success for a replay that is already complete. Verification of webhook signatures protects authenticity; it does not establish that an event is new. If events arrive out of order, transitions must prevent older information from reversing a confirmed state.
Implementation checklist
- Verify provider signatures where supported
- Store and check processed event identifiers
- Reject impossible backward state transitions
4. Retry transient failure without flooding the dependency
A network outage merits bounded retry with exponential backoff and jitter. Invalid input or revoked credentials usually need investigation instead. A dead-letter or failed-jobs queue must surface enough context to safely replay an operation, without revealing secrets or personal data in logs. Retrying an action without deduplication is not a reliability strategy.
Implementation checklist
- Classify retriable versus permanent errors
- Limit attempts and add backoff
- Expose operator recovery controls and alerts
5. Reconcile what the provider actually did
Events are not guaranteed to arrive once or on time. Periodic reconciliation compares internal expectations with provider truth, such as whether a notification was accepted or an order was paid. Repair processes should record who corrected what, why and when. This is particularly important when an upstream service changes webhook delivery or the application experiences partial downtime.
Implementation checklist
- Compare business statuses against provider records
- Provide audited repair or replay paths
- Alert on aged pending records, not only 500 errors
6. Test failures before declaring the workflow production-ready
I focus tests on intentionally duplicated events, delayed callbacks, transaction failures, access control and recovery after worker restart. My Fitter Health case study documents notification reliability work in a production application; the separate system-design examples explain how I reason about broader message delivery boundaries. A passing example does not prove arbitrary infrastructure scale.
Implementation checklist
- Build integration tests for duplicate delivery
- Verify rollback and worker restart behavior
- Record time to detect and repair stuck work
Architecture decision
Do not add Kafka, a saga framework or dozens of microservices by default. Use the smallest durable mechanism that fits the consistency and recovery requirements.
Technical references
Primary documentation for the design principles in this guide. Project evidence above describes the implementation.
- Django: database transactions
Transaction boundaries and running callbacks after a successful commit.
- Apache Kafka: message delivery semantics
Delivery guarantees and the limits of coordinating consumer state with external systems.
Apply this to a real product
Explore related engineering capabilities, all technical notes, or contact me about an engineering challenge.
Let’s talk
I like working through complex problems with people who care about the details. Have a product to build, an engineering role, or an interesting challenge? Let’s start a conversation.
SaaimOpen to full-time roles, contract work, and conversations about things worth building.