Power Automate | Design Every Flow So a Resubmit Is Always Safe

From the archive. This post dates from the blog’s Dynamics GP and early Power Platform era. Products and screenshots may have changed since it was written, and some of its links (PartnerSource and CustomerSource pages, legacy Maximum Global Business downloads, and other retired sites among them) may have been archived or may no longer be available.
Design every Power Automate flow so a resubmit is always safe

If there is one question I have learned to ask of every single action in every Power Automate flow I build, it is this one: what happens if this runs twice?

Throughout our Business Central to Dynamics 365 CE billing integration, the same project from my previous articles in this series, we made the answer to that question a design requirement. Every flow had to produce the same correct end state whether an event arrived once, twice, or five times, and whether the runs came from the webhook, a retry, or a human clicking resubmit weeks later. So, in this article I will walk through why that requirement pays for itself many times over, the patterns that satisfy it, and the two match-key mistakes we made on the way to getting it right. Yes, it took us three attempts!

Why events arrive more than once

Keep in mind, if your integration is triggered by webhooks, replays are not an edge case. They are the operating environment!

  • The source system fires on modification, not creation. Our flows trigger on Business Central's customer ledger, where a single business event can touch several entries, and each touch fires the trigger. Guards filter the noise, but the same document can legitimately come through the front door repeatedly.
  • Repairs happen by resubmission. When a run fails -- a permissions gap, a defect, an outage -- the natural fix is: repair the cause, resubmit the stored run. The trigger payload replays against the flow as it exists now.
  • Testing happens by resubmission. The fastest way to verify a flow change against real data is to resubmit a known run and inspect the result. If reprocessing old events corrupts data, you have lost your best testing tool.
  • Late events reprocess old ground. A payment applied to an invoice months after posting triggers a reconcile over records your flow already created.

Now, you may be asking, "can I not just guard against each of these individually?" You certainly can, and you will be maintaining those guards forever. Or you can adopt the single stronger property that covers all of them at once: every run converges the target system to the source system's current truth, no matter how many times it executes.

Sync the state, not the event

The foundational pattern is a mental shift. An event-applying flow says "an invoice was posted, so create an invoice." A state-converging flow says "an invoice exists in the source; make the target match it." The first duplicates on replay. The second cannot, because its writes are defined relative to what is already there.

Concretely, our invoice sync is a reconciling upsert. It looks up the target invoice by the source system's document number. If it does not exist, it creates it with its lines. If it does exist, it updates the header and then reconciles the lines: match each source line to a target line, update the matches in place, create the missing, and delete the orphans that no longer exist in the source. The payment sync works exactly the same way, reconciling the payment's application detail rows against what the source says is currently applied, deleting rows the source no longer has, right down to deleting the payment header itself when zero applications remain.

In turn, a reconcile has a property an append never has: it is self-healing. When we later fixed a line-mapping defect, the reconcile did not just handle new invoices correctly; rerunning it against previously damaged invoices replaced the malformed lines with correct ones. The repair tool was the flow itself!

Let the source system name things

Idempotency needs a key, and the right key is the source system's own identity for the record, carried into the target where the flow can find it again. Our payment headers are named by the Business Central document number, which makes the name double as the idempotency key: any rerun finds the header it created before. Invoices are looked up by the posted invoice number. Nothing is keyed by anything the flow generates, because a generated key is different on every run, which is precisely the property you do not want.

Getting this right at the line level took us three attempts, and the two failures are worth studying, because they will save you from repeating them.

Attempt one: match lines by name. As it turns out, this failed immediately, because the target system overwrites the line description with the product name on product-based lines. Our matching field was being silently rewritten by the platform after every create, so matches always missed, and every reconcile deleted and recreated every line. The lesson: never match on a field the target system considers its own to modify.

Attempt two: match lines by product ID. This one worked for months. Then it failed structurally the day we met an invoice with two lines sharing one product -- a maintenance credit billed as a negative line on the same product code it credits. The reconcile saw the product already present and concluded the credit line was already synced. It was not a bug in the matching code; the key itself could not distinguish the rows. Worse, the failure mode was silent: the reconcile ran, reported success, and structurally could never repair the missing line. Suffice to say, a key that is merely usually unique is a defect with a delayed fuse.

Attempt three: match lines by the source system's line number, stamped into a dedicated field on every line the flow writes. Business Central's line numbers are the source's own identity for the row: unique within the document, stable across time, and meaningless for the target platform to overwrite. Every line create and update stamps it; matching and orphan detection key on it. This is the version that survived every scenario we could throw at it.

The general rule that falls out: the match key must be the source system's identity for the row, persisted in the target, and owned by nobody else.

A quick aside before moving on: both failures were diagnosed in minutes rather than an afternoon of clicking through run history, because we develop these flows with Claude Code reading run actions directly through FlowAgent. The agent walked the failed runs, found the exact action and input responsible each time, and in the second case also exposed that the product-keyed reconcile could never have repaired the damage. That workflow is a story of its own, and I told it in my FlowAgent article; here it is enough to say that resubmit-safe design and run forensics compose beautifully -- one gives you a safe repair tool, and the other tells you precisely when to use it.

Gate the side effects on change

Data writes converge naturally under a reconcile, but side effects -- notifications, timeline notes, cascade triggers -- duplicate cheerfully unless you design them not to. Two patterns handled every case we had:

Attach side effects to transitions, not to runs. Our settlement timeline notes are written only when a detail row is actually created, only when a synced row is actually deleted, and only on the first sighting of a cancellation, guarded by "the invoice is not already cancelled." A resubmitted run that changes nothing writes no note, because no transition occurred. We proved this on a live rerun: identical resubmit, zero writes, zero duplicate notes. The note trail reads like a history precisely because it records transitions.

Write only when the value differs. Our bundle cascade flow updates a child record only if its expiration differs from the parent's or the row is inactive. That single guard makes the cascade convergent: reprocessing is free, and a chain of cascading updates terminates on its own -- five runs, converged, no runaway, in our live verification. As a bonus we did not initially design for, the flow can safely react to writes made by other flows sharing its connection identity, because reacting to an already-correct value is a no-op.

The payoff: resubmit becomes your universal tool

Once every flow held the property, resubmission quietly became the answer to almost everything:

  • Repairs. When the credit-line defect left two invoices wrong in the target system, the repair was: deploy the fix, resubmit the two failed runs. Both invoices converged to the source's truth to the penny, replacing the malformed legacy lines on the way. No repair script, no manual edits!
  • Environment fixes. When a run failed on a missing privilege in a fresh environment, the fix was the role assignment plus one resubmit, which completed the scenario and re-proved reconcile idempotence in that environment for free.
  • Verification. Every flow change was tested by resubmitting a stored run and reading the results, precisely because reprocessing was guaranteed harmless.
  • Locked-record handling. Even where the target system makes records read-only -- paid invoices, in our staging environment -- the reconcile wraps its work in a capture-state, reopen, sync, restore sequence, so a resubmit against a paid invoice succeeds and leaves the record exactly as it found it.

Two caveats from the field

NOTE: a resubmit replays the trigger payload against the current flow definition. That is exactly what makes fix-then-resubmit work, but it has a sharp edge: if a deployment has left the flow stopped, a resubmit silently replays against the old definition, and the result can masquerade as a test outcome. It happened to us exactly once, which was enough to add "verify the flow is active and read the definition back" to the deployment checklist.

NOTE: idempotency does not replace concurrency control. Convergent runs racing each other can still interleave reads and writes. We run our webhook-triggered flows at concurrency one, not because the design needs it to be correct in sequence, but as cheap insurance that runs actually execute in sequence. The two properties compose: idempotency makes replays safe, and serialization makes overlaps impossible.

The checklist

For every flow, before it ships, walk through the following five questions:

  1. Can I resubmit any historical run right now with no damage? If not, why not? Fix that first.
  2. Is every record the flow writes findable by a key the source system owns?
  3. Does the flow reconcile to current source truth, including deleting what the source no longer has?
  4. Is every notification and note attached to a transition, not to a run?
  5. Does every cascading write fire only when the value actually differs?

When it's all said and done, the cost is a habit of mind during design, and the return is an integration where the scariest phrase in operations, "just run it again," is not scary at all. It is the standard procedure, and it is always safe.

If you have your own resubmit war story, or a match key that failed you in a way I did not list here, please drop a note in the comments describing your experience; idempotency lessons are expensive to learn firsthand and free to learn from each other.

Until next post!

MG.-
Mariano Gomez Bent
Former Microsoft BizApps MVP

Comments

Microsoft MVP Alumni