CharityStack
Menu
← Blog

A repeated webhook should not repeat the donation workflow

Define durable event acceptance, concurrent processing protection, and downstream action keys so webhook retries do not repeat donation work.

C

CharityStack

·4 min read

Editorial card over an Aurelia painting of a sunlit garden opening onto a turquoise sky.

Donation webhook idempotency means that receiving a duplicate event does not repeat the same donation-related effect. Ask your technical owner to keep a durable event record, protect concurrent processing, and track downstream actions separately before connecting webhooks to gift creation or acknowledgments.

This is an implementation brief for the person maintaining the integration. A nonprofit operator can use it to ask for evidence that retries will preserve one intended result.

Identify what is actually repeated

Separate the event identifier, the delivery attempt, and the payment identifier. One event can arrive through more than one attempt. One payment can generate multiple different events. Neither the donor's email address nor the gift amount identifies a unique event.

Stripe's webhook documentation warns about duplicate event deliveries and, in some cases, separate event objects describing the same underlying object and event type. It also says delivery order is not guaranteed. These are Stripe-specific documented behaviors; ask for the equivalent guarantees from your provider.

Choose the deduplication key at the right boundary. Record a provider event key with its account and environment so a test event cannot be confused with a live one. For a downstream gift-creation action, also define a stable business key for the intended payment operation. Do not suppress a legitimate later refund merely because the payment identifier was seen before.

Accept the event durably before acknowledging it

Have the technical owner verify the request signature with the provider's supported method. Then persist the accepted event or put it in a durable queue before returning a success response. If that durable acceptance fails, the endpoint must not report success as though the work is safely stored.

Stripe recommends prompt successful responses and asynchronous event handling in its webhook guidance. The design recommendation here is to keep durable acceptance short and move slower business work to a worker. A successful HTTP response should mean the integration has accepted responsibility, not that every downstream action already finished.

Record enough information to trace acceptance, processing, completion, and failure without storing unnecessary donor details. Keep event data under the organization's access and retention rules.

Protect against two workers at once

A read-then-write check is insufficient if two workers can both read “not processed” before either writes a result. Ask for a database uniqueness constraint, conditional write, or equivalent atomic operation that reserves the processing key.

AWS Powertools' idempotency documentation describes persistent records, in-progress handling, and concurrent-request protection. That is a reference implementation for AWS Lambda, not a requirement to use that library or a guarantee for an unrelated database.

Define what happens if the worker stops after claiming the key. A forever-locked event can lose work; an immediately reopened claim can duplicate it. The recovery design should distinguish an active worker from an abandoned attempt and retain a retryable failure state.

Where the event record and gift mutation live in the same transactional database, ask whether the intended local change and completion state can commit together. Where they cannot, document the gap and the recovery mechanism explicitly.

Track external actions at their own boundary

Writing a local “complete” marker does not by itself prove that an external receipt, CRM update, or other action happened exactly once. A worker can stop after the external service accepts the action but before the local marker is saved.

Use a stable operation identifier and the destination's supported deduplication facility where available. Otherwise maintain a delivery record and investigate uncertain outcomes before repeating the action. Ask the technical owner to explain this failure window rather than accepting an unqualified exactly-once promise.

Keep gift identity clear when applying the event. The existing CRM mapping guidance defines which record belongs where; idempotency decides whether a specific operation has already been applied. The donation import controls cover a related duplicate risk through a different intake path.

Require a small set of meaningful proofs

In an isolated test environment, demonstrate repeated delivery, simultaneous workers, failure before durable acceptance, failure after a local write, and an older event arriving after a newer state. Keep external messaging and payment actions suppressed during the test.

Inspect both the event trail and the final business records. The same event should not create another gift, while a legitimate different event must still perform its intended update. Finish with a named recovery owner and a documented way to inspect uncertain side effects. That makes the integration's promise concrete enough to review.

Make room for fundraising

Explore CharityStack for your next fundraising campaign.

Start Fundraising Free →