Avoid duplicate invoices with stable identity: Bind invoice creation to tenant + order ID + invoice type for uniqueness.; Use idempotency keys in billing APIs like Stripe to prevent retries creating duplicates.; Test retry scenarios with interrupted workflows to confirm one invoice is created.
Image: Workflow Automation Guide

Error Handling

Part of Idempotency and duplicate prevention

Preventing duplicate invoices after a workflow retry

Protect invoice creation across workflow retries, lost responses and concurrent workers using a stable business identity.

To avoid duplicate invoices, bind invoice creation to a stable business identity, such as the approved order and invoice purpose. On retry, reuse that identity and find the invoice already created, even if the first workflow attempt lost its response. Treat the invoice system as part of the safeguard: a local “processed” flag alone can miss a failure between systems.

Trace the uncertain outcome

Suppose a workflow sends a create-invoice request, gets no response and retries. The first request may have failed, or it may have succeeded and its response was lost.

If the second attempt creates a fresh invoice, the workflow has turned uncertainty into a duplicate. The same problem arises when two workers pick up the same event. Azure’s messaging guidance describes repeated delivery and recommends idempotent consumer processing.

List every place an invoice can be created: the workflow, a human retry button, another integration and the billing API itself. If any path bypasses the identity check, the protection is incomplete. Decide whether an amended invoice, recurring billing period or separate purchase is a new action; those must remain possible.

Key Metrics for Invoice Idempotency

Idempotency Key Retention (Stripe)
24 hours minimum
Risk of Duplicate on Retry Without Idempotency
High – can lead to double billing
Azure Messaging Guidance on Repeated Delivery
Recommend idempotent consumer processing

Use a stable invoice identity

A practical design might record tenant + order ID + invoice type as a unique invoice request. The exact fields depend on the billing rules. Save the request identity, its status and the resulting invoice ID. When the same request arrives again, return or look up that invoice instead of issuing another create call.

If the billing API supports an idempotency key, pass the same key on each retry of that call and follow its documented retention and conflict rules. Stripe’s published API contract is one example: repeated matching requests with the same key return the recorded result, but keys can be pruned after a minimum retention period. Do not assume this protects a second billing provider or an invoice created manually.

Two concurrent attempts also need handling. A “check then create” lookup without a unique constraint or transactional claim can let both workers proceed. Make the identity unique in a durable store, and define what the losing worker reads or returns. If the first worker is still running, distinguish “pending” from “complete” rather than marking the work done too early.

Reconcile after a partial failure

If the external invoice exists but the local workflow never recorded its ID, search the billing system by the stable reference or retry with the same supported key. Create a new invoice only when the earlier outcome is resolved. For a system without a reliable lookup or idempotency contract, route uncertain cases for review instead of blindly repeating the create step.

Test the exact failure window: create an invoice, interrupt the workflow before it records success, then retry. Confirm the billing system contains one invoice and the workflow points to it. Repeat with simultaneous workers and with a legitimate second invoice type. This verifies the business result, not merely that the retry step displayed “success”.

More from Error Handling