Back to Blog

Headless WooCommerce Coupon Sales Across Two Stores

A headless WooCommerce coupon sale spans two stores and an unreliable network. The design choices that make fulfilment recoverable after payment.

Jakub Czechowski

Builds websites and e-commerce at JC Web Studio, runs StackCompass – a publication on content architecture and stack decisions – and co-organizes CMS Conf, a conference on content systems.

/ / 4 min read

A customer clicks buy, pays, and expects a coupon. The sales store marks the order complete, but the coupon may not yet exist in the store that validates it. Those are two facts separated by a network, two databases, and a short period in which the confirmation page is ahead of reality.

That gap was the real project. Selling coupons through WooCommerce is familiar territory; fulfilling them in another store is not a digital download but a promise between systems. Once two stores are involved, retries, intermediate states, and recovery stop being optional extras.

Splitting the Stores Isolates Operational Risk

The default should be one WordPress install: one database, one checkout, coupons generated locally. For a single-store operation, that is usually the right answer. We accepted the cost of separation because the existing coupon store already carried the business-critical logic, historical data, and live redemption traffic.

Adding a public sales funnel to that install would also add campaign spikes, marketing plugins, and a faster deployment cadence. A bad release on the selling side could then affect the system that issues and honours coupons.

The split created two failure domains. The sales store can be rebuilt or temporarily overloaded without taking coupon redemption down with it. The coupon store remains conservative and changes less often. This is the same reasoning behind the deliberate architectural decisions a closed B2B WooCommerce build makes: separation is useful only when the boundary pays for its operational cost. Here it paid in risk isolation, not elegance.

The Coupon Is Created After Payment

With a conventional download, the asset usually exists before payment. A coupon issued in another store may not. Creating it requires a cross-system call that adds latency and can fail.

Waiting for that call inside checkout would couple conversion to the coupon store’s availability. A transient network failure could become a failed purchase. We chose a different contract: confirm payment quickly, create the coupon asynchronously, and tell the customer that fulfilment is still in progress.

That creates an explicit intermediate state: paid, but not yet fulfilled. It is expected, not exceptional. The system stores and exposes that state so it can recover instead of pretending the sale and the coupon creation form one atomic transaction.

Astro Owns Discovery; WooCommerce Owns Checkout

The buying funnel is mostly read traffic: browse the offer, compare coupons, and move towards purchase. It does not need WordPress’s template stack or a database query on every page view. We used an Astro front tuned for speed and connected to a WordPress back end for discovery and listings, with catalogue data coming from the WooCommerce REST API.

This was another boundary decision, not a commitment to make everything headless. Astro owns the cacheable, marketing-shaped surface. WooCommerce still owns cart and checkout, where payment integrations, tax, and order state already work. Rebuilding that path would add another custom system at the most sensitive point in the flow.

Most listing pages were static and revalidated when the catalogue changed. Dynamic rendering remained limited to data that had to be current. The result was fast where caching helped and conventional where WooCommerce reduced risk.

A Webhook Signals Work; It Does Not Confirm Fulfilment

The obvious notification path is a webhook: the sales order completes, the coupon store receives an event, and fulfilment begins. That is useful for low latency, but webhook delivery alone is not a durable workflow queue.

The receiver may be slow, unavailable, or mid-deployment. Even if the sender retries, delivery and processing are separate facts: an accepted request can still fail before the coupon is created. A webhook should therefore mean “work may be available,” not “fulfilment definitely happened.” Treating those as the same fact is how integrations lose a small number of orders without noticing until a customer complains.

Retries Require Idempotency and Reconciliation

The reliability model is: acknowledge quickly, process asynchronously, and reconcile. The inbound endpoint returns before doing cross-store work. Coupon creation runs in a background process that can fail and be retried, with exponential backoff and jitter to avoid hammering a struggling receiver.

Retries require idempotency. If “create the coupon for this order” runs twice, it must return the same result rather than issue two coupons. Each job therefore carries a stable identity that the coupon store recognises. Cross-store calls are also signed and verified, while the idempotency key makes legitimate replays safe. The implementation can vary; the invariant is that every operation is safe to repeat.

Beneath the event path sits periodic reconciliation. It asks which paid orders still lack a fulfilled coupon and schedules them again. This catches dropped notifications, deployment windows, and failures after acknowledgement. Events make the process fast; reconciliation provides evidence that paid orders reached the required final state.

Cross-Store Fulfilment Is the Hard Part

WooCommerce’s native mechanism handled coupon generation. The engineering work was making that operation reliable across a system boundary.

The better question is not “how do we sell coupons in WooCommerce?” It is “what states can exist after payment, and how does each one reach fulfilment?” Once that question is explicit, the two-store split, the hybrid headless front, idempotent jobs, and reconciliation become one coherent design rather than a collection of integration features.