ConicPlex

Start Your Project

A backend developer desk at night with dual monitors showing terminal and log windows, a keyboard, and a coffee mug, with a server rack visible through a glass wall in the background

On this Page

Why Marketplace Platforms Need Idempotent Webhooks to Scale

Sajil Memon

October 21, 2026

A webhook that fires once in testing will fire twice, three times, or out of order in production. That is not a bug in Stripe, Shopify, or whatever payment processor a marketplace platform is built on. It is how webhook delivery works by design: providers favor delivering an event more than once over risking that it never arrives at all. If a marketplace platform’s backend treats every webhook delivery as a fresh, unique event, it will eventually double-capture a payment, flip a job’s status twice, or pay out a contractor for work that was already settled. Idempotent webhook handling is the fix, and it has to be built in from the start, not retrofitted after the first duplicate-charge support ticket.

We ran into this directly while building CasaFixx, a two-sided home services marketplace that connects homeowners posting jobs with verified contractors who submit proposals, get hired, and get paid through Stripe. Jobs move through a handful of states: posted, proposed, accepted, in progress, completed, paid. Every one of those transitions can be triggered by a webhook. Every one of those webhooks can arrive more than once.

Why Webhooks Retry in the First Place

Most engineers building their first payment integration assume a webhook is delivered once, like a function call. It isn’t. Providers queue an event, send it, and wait for a 200 response. If that response is slow, dropped, or arrives after the provider’s timeout window, the provider assumes delivery failed and sends the event again, even though your server already processed it the first time. According to Activepieces’s 2026 writeup on webhook idempotency, Stripe keeps retrying a failed delivery for roughly three days, while Shopify will attempt redelivery around nineteen times over 48 hours before giving up on an endpoint. GitHub, by contrast, doesn’t retry at all, which means a dropped webhook there is just gone. Three different providers, three different retry philosophies, and a marketplace platform is usually wired into more than one of them.

This is the part that catches teams off guard: a 200 response sent 5.1 seconds after a request with a 5.0-second timeout reads, to the provider, exactly like no response at all. Prismatic’s engineering blog calls this out specifically, noting that a slow acknowledgment or a connection drop right as the response is sent will trigger a duplicate delivery even though your server did everything correctly the first time. The event wasn’t lost. The acknowledgment was.

What Actually Breaks Without Idempotency

On a marketplace platform with a partner dashboard like CasaFixx’s, the failure modes aren’t abstract. They show up as real support tickets:

  • A payment_intent.succeeded webhook fires twice, and the order logic runs a second payout to the contractor, or marks the job paid-and-unpaid in two different tables that fell out of sync.
  • A proposal-accepted webhook retries after a slow database write, and the contractor’s dashboard shows the same job accepted twice, duplicating a notification and sometimes a calendar entry.
  • A job-completed event arrives out of order relative to a later cancellation, and whichever one happens to process last wins, regardless of which actually happened last in real time.

None of these require a bug in Stripe’s infrastructure or a mistake in the original request. They happen because the receiving code assumed “received” meant “new,” and that assumption breaks the first time a retry shows up in production traffic that a staging environment never generated.

The Two Layers That Actually Fix It

Idempotent webhook handling isn’t one trick. It’s two separate layers, and skipping either one leaves a gap the other doesn’t cover.

The first layer is deduplication: storing the unique event ID that every major provider includes in its payload, and checking that table before doing any work. If the ID has been seen, the handler returns a 200 immediately and does nothing else. The second layer is idempotent business logic: writing the actual state change (mark job paid, trigger payout, update status) as an operation that produces the same end result no matter how many times it runs, usually through an upsert rather than an insert, or a conditional update that only applies when the current state still matches what the event expects.

Both layers matter because each one fails differently on its own. Deduplication alone breaks if the process crashes after marking an event as seen but before finishing the work it was supposed to do; the retry that would have completed the job now gets silently discarded. Idempotent logic alone works correctly but repeats expensive side effects, like an email notification or a third-party API call, on every single retry, which is wasteful even when it isn’t technically wrong. A dedup check that isn’t wrapped in a transaction has its own race condition: two near-simultaneous deliveries can both pass the “have we seen this?” check before either one finishes writing the result.

Approach What it catches What it still misses
Deduplication only (event ID table) Exact duplicate deliveries of the same event A crash mid-processing after the event is marked seen but before the work finishes
Idempotent logic only (upsert/conditional update) Correct final state even with duplicate or out-of-order events Repeated side effects like emails, notifications, or third-party calls firing on every retry
Both layers together Duplicate deliveries, crash recovery, and repeated side effects Still needs a transactional write so the dedup check and the state change succeed or fail as one unit

For out-of-order events specifically, the safest pattern we’ve found is to stamp every webhook payload with the provider’s own event timestamp and only apply an update when that timestamp is newer than the one already stored against that record. A cancellation that arrives after a completion, by timestamp, overwrites it. One that arrives before it, even if the HTTP request lands later, gets discarded. That single rule resolves most of the ordering problems that a naive “process in arrival order” approach gets wrong.

Where This Shows Up in a WordPress-Built Marketplace

CasaFixx runs on WordPress with a custom plugin handling the job marketplace, the proposal system, and the partner dashboard, with Stripe wired in for payments between homeowners and contractors. That’s a common shape for a marketplace built through our plugin development work: a platform that didn’t start as a from-scratch application, and doesn’t need to, but still has to behave correctly when money and job status are both changing through asynchronous events. A WordPress REST endpoint receiving Stripe webhooks has to do the same dedup-and-upsert work any custom backend would; the framework doesn’t make duplicate deliveries go away, and a raw UPDATE query without a WHERE clause tied to the event’s own state is exactly the kind of non-idempotent write that breaks under a retry.

This isn’t unique to payments, either. We ran into a related version of the same problem on a different project, syncing a 32,000-SKU WooCommerce catalog against an external inventory feed, where idempotent matching and dry-run testing before anything goes live turned out to be the difference between a sync that could be re-run safely and one that silently duplicated products on every retry.

Booking and appointment-based marketplaces have an even narrower margin for this kind of failure, since a duplicated “confirmed” event can double-book a slot that a human then has to manually unwind. If you’re building anything in the booking and appointments space with webhook-driven status changes, the dedup table and the upsert pattern aren’t optional hardening, they’re part of the core design.

What It Costs to Get Right, and What It Costs to Skip

Building the dedup table and writing state changes as upserts instead of inserts adds maybe a day or two to a marketplace backend’s initial build, assuming the data model was planned with it in mind. Retrofitting it after launch costs considerably more, because by then there’s almost always inconsistent data to reconcile: jobs marked paid twice, contractors who received a payout for an order that later got refunded, status fields that disagree with each other across tables. We’ve seen that reconciliation work take longer than the original webhook handler did.

The CasaFixx build, like most of the marketplace platforms we’ve built, treated the dedup table as part of the initial database schema rather than a later patch, which is the cheaper order to do it in every single time. If a marketplace is still early enough that its webhook handlers haven’t been load-tested against real retry traffic, that’s the moment to add this, not after the first ticket about a double payout. For a platform already live and already seeing duplicate-state bugs, the fix is the same two layers described above; it just has to be applied against data that may already be inconsistent, which means a reconciliation pass before the new logic goes live, not after.

Frequently Asked Questions

What is a webhook idempotency key?

It’s a unique identifier, usually the event ID a provider includes in its payload, that a server stores and checks against before processing an incoming webhook. If that ID has already been recorded, the handler skips reprocessing and simply returns a success response, which prevents the same event from triggering a duplicate action.

Do I still need this if I’m just using WooCommerce and a Stripe plugin?

Mostly yes, though less of it falls on you directly. Established plugins generally handle Stripe’s own webhook dedup internally. The gap shows up in custom logic layered on top, like a notification, a payout trigger, or a status change in a separate plugin, that fires again on a retry because it was never written to check whether it already ran.

What actually happens if a payment webhook fires twice and nothing is deduplicated?

It depends on what the handler does with it. If it triggers a payout or sends a charge-confirmation action, that action can run twice. If it just updates a status flag, the second write is often harmless because it sets the same value again. The danger is in handlers that take an action with a real-world side effect rather than ones that only set state.

Is deduplication enough on its own, or do I need idempotent logic too?

Both. Deduplication alone fails if your process crashes between marking an event as seen and finishing the work tied to it, since the retry that would have completed that work gets discarded as a duplicate. Idempotent logic alone works but can repeat costly side effects like emails or third-party API calls on every retry. Together, they cover each other’s gaps.

Sources

Sajil Memon is a co-founder of ConicPlex and a Senior Full Stack Developer focused on backend work: Node.js, PHP, APIs, and the infrastructure decisions that determine whether a system holds up under real traffic. He’s spent years on the side of a project that doesn’t get demoed, the part that has to keep working after launch. He writes here about the technical tradeoffs in backend architecture, deployment, and data handling that only become obvious once something breaks in production.

Leave a Reply

Your email address will not be published. Required fields are marked *

Keep reading

News & Updates

A laptop and VPN server rack glowing in navy and teal light, evoking the Ivanti Connect Secure zero-day

Ivanti Connect Secure Zero-Day Lets Attackers Chain Three Flaws to Root (CVE-2026-104935)

Ivanti patched an authentication bypass flaw in its Connect Secure VPN appliances on October 13, 2026, five days after a…

Sameer Malek

October 21, 2026

News & Updates

Laptop glowing with magenta light on a desk at night, representing a WordPress SEO plugin security incident

Rank Math PRO’s Supply Chain Breach Left a Self-Restoring Backdoor on WordPress Sites

A supply-chain breach at Rank Math let malware ride along with Rank Math PRO version 3.0.123 for roughly 11 hours…

Aftab Memon

October 20, 2026

Plugins

Flat design illustration of stacked notification toast cards with a shopping bag icon and a pulsing activity ring, representing live sales notification popups for WooCommerce

How to Add Live Sales Notifications to WooCommerce (4 Plugins Compared)

Compare 4 real WooCommerce live sales notification plugins: PiWeb, NotificationX, Notivo, and Salert, with real pricing, setup steps, and honest…

Husen Memon

October 20, 2026

WhatsApp
Husen Memon
Husen Memon
Typically replies instant