Two expense claims land on a manager’s desk on the same afternoon.
The first says: “Expense claim. Please approve.” To decide, the manager has to open the receipt, find the policy, work out which client it was for and check the amount against the limit. They are busy. They click approve.
The second says: “Taxi, RM 48, client visit to the Shah Alam office on 3 November. Within the RM 60 limit. Receipt attached.” The manager reads it in five seconds and decides properly, either way.
Both requests asked for the same thing. Only one of them made a real decision possible. An approval step in an AI employee’s workflow works exactly the same way: the step is only as good as the request it puts in front of a person.
We have already written about which actions should wait for a person at all and what happens when nobody answers in time. This post is about the request itself: how to design it so the manager decides quickly and well, instead of clicking through.
First, an approval is a step you add
In Wetel, an approval is a node you place in a workflow. It is not switched on for you: if you do not add the step, the AI employee does not stop to ask. So the choices below are yours, made for the actions that matter.
When the run reaches the step, it parks, a request opens, and a workspace owner approves or rejects it. The run then continues down one of three labelled paths: approved, rejected or expired.
flowchart LR T["response: I've asked a manager"] --> P{{approval}} P -- approved --> A["do the action, then confirm"] P -- rejected --> R["explain, include the comment"] P -- expired --> E["someone will follow up"]Write a title that is the decision
The title is what the approver sees first, in the approvals inbox and in the in-app notification. The field is titleTemplate, a template filled in from the run’s context, so it can carry the real values of this case. It is cut at 200 characters.
“Refund request” tells the owner only that work is waiting. “Refund of 40 for order 8841” tells them what they are deciding before they open anything. With several requests waiting, that is the difference between a list someone can triage and one they work through blindly.
A useful test: if the owner only ever read titles, could they at least tell which requests are urgent or unusual?
Write a summary that makes opening anything else unnecessary
The summaryTemplate is the longer text the approver reads (up to 4,000 characters, also filled in from context). The aim is the second expense claim: everything needed to decide, in the request itself.
Here is a weak version, close to what most first drafts look like:
{ "titleTemplate": "Refund request", "summaryTemplate": "The customer wrote: {{userMessage}}", "saveAs": "approval"}It passes the customer’s last message along and leaves the owner to work out the rest: which order, how much, why, and what approving will actually do.
And a stronger one. Here refund is a context variable an earlier step in your workflow filled in (for example an action node with outputVar: "refund"); the field names inside it are yours:
{ "titleTemplate": "Refund of {{refund.amount}} for order {{refund.orderId}}", "summaryTemplate": "Customer asked for a refund of {{refund.amount}} on order {{refund.orderId}}.\nReason they gave: {{refund.reason}}\n{{#if refund.note}}Note from the AI employee: {{refund.note}}\n{{/if}}If you approve, the refund is issued in the next step and the customer is told. You can change the amount before approving. If you reject, the customer is told it was declined, with your comment.", "payloadVar": "refund", "editableFields": ["amount"], "saveAs": "approval", "expiresAfterSeconds": 86400}Three things make the second one work:
- The facts the decision rests on, filled in from context rather than paraphrased by a model. A number shown to an approver should come from the step that produced it.
- The consequence of each button. “If you approve, X happens; if you reject, the customer hears Y.” People decide better when they can see what their click does.
- What they can change. If the approver may correct a value, say so in the summary, so they know editing is an option and not only yes or no.
Keep it short enough to read. 4,000 characters is a limit, not a target.
Put the thing being decided in the request, and allow edits only to named fields
The payloadVar field names a context variable holding the object under decision, such as an order or a draft refund. The approver sees it alongside the summary. It is limited to 64 KB, and secret-shaped values in it are redacted.
editableFields lists the keys of that payload the approver may change while approving. Any edit to another key is refused. This is a design decision, not a formality: decide which values a manager may reasonably correct (an amount, a date, a line of wording) and which must stay as they are (an order id, a customer id). If only the amount should change, list only amount.
Two behaviours to plan around:
- Edits count only on approve. A rejection that carries edits is accepted, and the edits are ignored.
- The workflow reads the decided version. After approval, the decision saved under
saveAsholds the payload with the edits laid over the original, so a later step uses{{approval.payload.amount}}, not the original value. Build the next step on the approved payload, not on the variable you started with.
Choose an expiry that matches the work
Every request has a deadline: expiresAfterSeconds, 72 hours by default, from 1 second up to 14 days. Pick it from the work, not from the default:
- A customer waiting in a live chat for a small refund needs an answer within a working day, or an honest “someone will follow up”.
- A quote for a large order can reasonably wait a few days for the right person.
A deadline that is too long leaves the customer in limbo. One that is too short trains everyone to expect expiries and stop looking. Once it passes, the request cannot be decided at all, even a moment later.
Write all three replies as a person would want to receive them
The approval node says nothing to the end user. Everything the customer hears comes from the steps you put around it, so write each one deliberately:
- Before the request: tell them a person is checking, and roughly what happens next. “Thanks, I’ve asked a manager to review your request.”
- Approved: confirm what was done, using the approved values. “Your refund of {{approval.payload.amount}} has been approved.”
- Rejected: decline politely and pass on the reason. The approver’s comment is available as
{{approval.comment}}(up to 1,000 characters). - Expired: be honest that nobody decided yet and say what happens now. Never do the risky action on this path.
One detail: when the approver leaves no comment, {{approval.comment}} renders as blank. Write the reply so it still reads well without it. “Sorry, we couldn’t approve this one.” should be a complete sentence on its own.
It also helps to ask approvers to add a short comment when they reject. It costs them a few seconds, and it is the only explanation the customer will get.
One request per consequential action
It is tempting to bundle: one request for “these five refunds”, decided once. A decision applies to the whole request, though. The owner approves or rejects it as a unit, so a bundle forces an all-or-nothing call, and the one questionable item hides among four routine ones.
Our default advice: one request per action that has its own consequence, with its own title and payload. Bundle only items that are genuinely the same decision. A run can hold at most 5 pending approvals at once, which is a useful reminder that a workflow asking for approval again and again probably needs redesigning, not more requests.
Know what the approver sees, and who the approver is
When a request opens, every active workspace owner gets an in-app notification titled “Approval needed”, and the request appears in the approvals inbox with its title, summary, payload, the editable fields, status and deadline. Owners can approve, reject with a comment, edit the allowed fields while approving, or cancel a request (which leaves the run paused for good).
Today’s limits, so you can design around them:
- Only workspace owners can approve. Approver roles beyond the owner are not built, and API keys cannot decide.
- No email notice. The node accepts an email option, but no email is sent yet. If owners do not check the app, requests will expire.
Every request competes for the attention of a few owners, one more reason to make each one quick to read.
Test every outcome before anyone sees it
In a draft test you can simulate the decision with draftApprovalOutcome: APPROVED, REJECTED or EXPIRED. The approval node completes at once, no request is created, nobody is notified, and the run follows only that path. Run all three and read each reply as the customer would.
One caution: plan as if the steps after a simulated approval run for real. If your approved path calls a business system, test it against something safe. More on drafts in how to try a workflow safely before customers see it.
Watch for approval fatigue in the records
Approval fatigue is when people approve without reading. It does not announce itself; you find it by looking.
Every decision is kept. The decision saved under saveAs records who decided, any comment, and the payload after edits, and it sits in the run’s context, which you can read in the run record. The inbox can also list requests by status, not only pending ones.
Every few weeks, look through them with a few questions in mind:
- Is everything approved, never edited, never commented? That can mean the AI employee is right every time, or that nobody is reading. If it is the first, that action may be ready to move down a rung and run without approval. If it is the second, the requests need rewriting.
- Are many requests expiring? The deadline may be too short, or the owners may not be seeing them.
- Are the same fields edited again and again? The AI employee is getting that value wrong, and an earlier step needs fixing.
- Do rejections come with reasons? If not, the customer is getting the bare decline, and you are losing what the manager knew.
There are no figures to hit. It is the habit of a manager reading a sample of past expense claims: checking that the control still means something.
Related reference docs
- Approval node: every field, limit and default, the three edge labels, and what
saveAsholds - Human approval: a walkthrough: build the graph, test it in a draft, decide, and let a manager edit before approving
- Draft tests:
draftApprovalOutcome: simulate each outcome without opening a request - Workflow design best practices: templates,
{{#if}}, and the custom helpers you can use in a title or summary - Observability: reading a run record and its context
To build your first well-designed approval step, follow Human approval: a walkthrough.