The requirement sounds simple. Before this action happens, a person should approve it.
The first implementation is always the obvious one. Catch the request, send a notification, and wait for the answer. It works beautifully in a demo, where the approver is the person giving the demo and they are holding their phone.
Then it goes to production and you discover what you have built. Every pending approval is a held connection. The approver is in a meeting, so it is held for forty minutes. Ten of those and your connection pool is exhausted. A load balancer times out at sixty seconds and returns an error the caller cannot distinguish from a real failure, so it retries, so now there are two approvals for one action. Someone restarts a process and every pending decision evaporates with no record of whether it was ever asked.
None of this is a bug in the approval feature. It is what happens when you model a human decision as a synchronous call, and it is why so many products have an approval feature that quietly nobody uses for anything important.
Refuse, ask, retry
There is a different shape, and it is old enough to be boring. Do not hold anything.
The request is refused immediately, with a refusal that says clearly this is not final, try again. The question goes to the person. When the caller tries again, it either proceeds because the answer was yes, or it is refused because the answer was no, or it is told to try again later because nobody has answered yet.
Squidder answers this way on every path it governs, which is why nothing is held open while a person decides.
Nothing waits. No connection is held, no thread parked, no timer running against a human's attention span. The state lives in a record of the question, not in a socket.
This is unglamorous and it is the entire trick. An approver who takes two days costs you two days of retries, which is nothing, instead of a resource leak.
What falls out of it for free
Restarts stop mattering. A pending question survives a deployment, because it was never in memory.
One question per action, not one per retry. A client that retries every few seconds does not produce a phone full of duplicate prompts, because the question already exists and the retry finds it.
Four honest outcomes instead of two. Waiting, approved, refused and expired are different things, and a caller that can tell them apart can behave sensibly. Collapsing them into success and failure is how you end up retrying a refusal forever.
Two more things follow, and together they are why this pattern reaches further than any approval feature does. Retrying a refusal is behaviour almost every client already has, because networks fail — so the control works on software nobody would ever have accepted a new integration into, including software you cannot change at all. And it scales down to no attention whatsoever: if nobody ever answers, nothing is consumed and the action simply does not happen, which is the correct outcome and costs nothing to sustain.
Where an exception is honest, and why
There is one case where holding briefly is the right answer: a protocol whose client has no way to retry, where a refusal is simply a failure. There, holding for a short bounded time is better than a control that cannot exist.
That exception has to be exactly one thing, bounded and written down, or it becomes the design. The moment it is easier to hold than to model the retry, everything drifts back to the version that exhausts your connection pool, and it does so one reasonable decision at a time.
What to ask a vendor
Not whether they support approvals. Everybody says yes.
Ask what happens to the request while the person decides. If the answer involves waiting, ask what the timeout is, what the caller sees when it fires, and what happens to the pending decision when a process restarts.
The answers will tell you within a minute whether they have run this in production.