Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 3 min read

One Repayment, Two Webhooks: Preventing Duplicate Loan Postings

A borrower makes one repayment. Your payment provider sends a notification, your system processes it, and the response times out. The provider sends the notification again. What stops the loan account from receiving the

One Repayment, Two Webhooks: Preventing Duplicate Loan Postings

A borrower makes one repayment. Your payment provider sends a notification, your system processes it, and the response times out. The provider sends the notification again.

What stops the loan account from receiving the same repayment twice?

That question belongs in your integration tests before launch.

I work at Lokta. This article presents an illustrative engineering approach, not a customer case study or a description of Lokta’s implementation.

Separate the notification from the accounting effect

A webhook tells your application that something happened. Your application still needs to decide whether that event should change a loan account.

There are two different duplicate checks:

  • Delivery check: Have we already received this provider event?
  • Posting check: Have we already applied this particular business operation?

These checks solve different problems. Two notifications can have different event IDs while describing the same intended repayment posting.

Stripe explicitly documents duplicate events and delivery without guaranteed ordering. Other providers may have different contracts, so check their documentation rather than assuming identical behaviour. Source: Stripe webhook documentation

Give each intended posting a stable identity

A payment ID alone is too broad. The same payment might later have a refund or reversal that requires a separate action.

Define a business key that identifies the intended effect. Depending on your integration, that could include:

tenant + provider account + payment reference + operation

The operation distinguishes the original repayment from a legitimate adjustment. Define these identities against your provider’s actual event model.

Enforce uniqueness in storage. A separate β€œcheck whether it exists, then insert” sequence can fail when two workers run concurrently.

Accept events durably, then process them

One possible design is:

  1. Verify the provider’s signature using its documented procedure.
  2. Store the accepted event in a durable inbox.
  3. Acknowledge receipt promptly after durable acceptance.
  4. Let a worker validate and process the event.
  5. Retain processing status and failure details for recovery.

Once you acknowledge an event, your worker needs a reliable retry mechanism. An event sitting in an inbox indefinitely is still an operational failure.

Close the crash window

If the posting and processing marker share a transactional database, commit them together.

When the ledger is a separate system, the failure becomes harder: the ledger might accept the repayment, but the worker could crash before recording success.

A durable outbox can coordinate dispatch, but messages may still be delivered repeatedly. The receiving system also needs an idempotency key or equivalent protection against duplicate effects.

Stripe’s request idempotency is an example of retry protection for outgoing API calls. It is a separate concern from deduplicating incoming webhooks. Source: Stripe idempotent requests

Illustrative animation of one repayment event being redelivered without a duplicate intended posting

Conceptual animation using synthetic identifiers: a repeated delivery reuses the completed result. This illustrates expected behaviour; it is not a recorded Lokta test or a product demonstration.

Test the failures deliberately

Before launch, test:

  • The same event arriving twice.
  • Two workers handling duplicates concurrently.
  • A timeout after a posting has committed.
  • A crash before the posting commits.
  • A legitimate refund or reversal for an existing payment.
  • An event that cannot be matched to a loan account.

Where to inspect the account outcome

The following Lokta demo screens show transaction and schedule records a team might inspect while checking a repayment. They were not captured during a webhook replay test and are not a before-and-after test result.

Lokta demo transaction history with transaction IDs, external reference, dates, amounts and principal outstanding

Lokta demo transaction history. Review transaction IDs, available external references, dates, amounts and principal outstanding while tracing a posting. This view alone does not establish webhook deduplication.

Lokta demo repayment schedule showing due dates, paid dates and demanded and collected amounts

Lokta demo repayment schedule. Compare the relevant periods and balance fields with independently calculated expected results. This is account context, not evidence that a duplicate-event test passed.

The expected outcome should be explicit: duplicate deliveries produce one intended accounting effect, while legitimate adjustments remain possible.

For lending teams turning these scenarios into vendor evaluation requirements, Lokta’s LMS RFP toolkit provides company-authored evaluation resources.

Start with one test: resend a successfully processed repayment event and inspect the resulting account entries.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.