DEV Community

Daniel Jonathan
Daniel Jonathan

Posted on

✅ Refund Decisions in Logic Apps: An AI Agent with Human-in-the-Loop

In this post, we'll build an agentic Logic App that decides whether
to approve, escalate, or deny a refund. The Agent applies policy,
pauses for human approval when needed, and emails the customer
with the final decision.


Why this pattern?

Most refund flows are a mix of clear rules (auto-approve low amounts
for valid reasons) and edge cases (needs a human). Logic Apps now
support an Agent action (Azure OpenAI) that can reason, call tools,
and control the flow --- perfect for policy + people.


What we're building (at a glance)

HTTP POST (refund request JSON)
   └─ Agent "RefundAgent" (Azure OpenAI gpt-4.1-mini)
       ├─ Tool: Send_Mail_For_Action (Outlook/Webhook) -> pause & wait for approver
       ├─ Tool: Send_Final_Status_Email (Outlook) -> notify customer
       └─ Tool: EndWorkflow (Terminate) -> clean run completion
   └─ Response 200 (optional output)
Enter fullscreen mode Exit fullscreen mode

🧩 Decision Policy

Auto-approve if:

  • Amount ≤ 200
  • Reason contains damaged, defective, or never arrived

Escalate if:

  • Amount > 200
  • Reason is unclear or outside policy

Deny if:

  • Reason violates policy (e.g., buyer’s remorse outside window)

The incoming request (HTTP trigger)

The Logic App expects a JSON payload like:

{
  "requestId": "REQ-2025-0918-001",
  "customerEmail": "alex@example.com",
  "orderId": "ORD-992311",
  "reason": "Item arrived damaged. Screen was cracked.",
  "amount": 149.99
}
Enter fullscreen mode Exit fullscreen mode

Here's a sample payloads ready for testing.


How the Agent thinks

  • System message (policy + format): tells the agent to produce a deterministic JSON decision and guide the flow.
  • User message: injects the raw trigger body (@{triggerBody()}) so the Agent sees requestId, email, reason, amount.

The Agent then chooses tools: - Call Send_Email_For_Action only when
a human decision is required. - Otherwise, jump to
Send_Final_Status_Email with a clear, customer-friendly message. -
End with EndWorkflow once the goal is reached.


Human-in-the-Loop (how the pause works)

Your Send_Email_For_Action tool uses: - Outlook "Mail with Options": sends an actionable email (e.g., Approve / Deny). - The workflow waits until the
approver clicks an option;tool resumes with the approver's choice.


The JSON (your workflow)

Trigger (HTTP POST)

  • Type: Request (kind: Http)
  • Schema: requestId, customerEmail, orderId, reason, amount


Agent action: RefundAgent

  • Model: Azure OpenAI gpt-4.1-mini
  • System: policy + human-loop instructions
  • User: includes the trigger body
  • Channels: Allow both input and output channels

🔄 Human-in-the-Loop Requires Real-Time Channels

To make the human approval loop (HITL) work correctly, you need to enable the following option in the Channels tab of your Agent action:

✅ Allow both input and output channels

  • This keeps the workflow active while the Agent is waiting on a human decision.
  • The approver’s response (Approve / Deny) is posted back to the callback URL, and because both channels are open, the Agent can resume in real time without restarting the workflow.
  • Without this option enabled, the workflow would end after the first response, and you couldn’t support HITL interactions.


Tool 1: Send_Email_For_Action

  • Purpose: Pause for human approval when the Agent isn't confident
  • Pieces:
    • Send_email_for_Approval_Or_Denial: Outlook Mail with Options subscription with:
      • To: approver (your demo address)
      • Subject: @{agentParameters('Subject')} #Your input is required#
      • Body: @agentParameters('Body')
      • Options: @agentParameters('User Options') (e.g., "Approve, Deny")

Tool Configuration

Send Email Configuration


Tool 2: Send_Final_Status_Email

  • Purpose: Notify the customer of the final decision
  • Inputs:
    • Subject: @agentParameters('Subject')
    • Body : <p class="editor-paragraph">@{agentParameters('Body')}</p>
    • To : the customer (your JSON uses a fixed list; parameterize to customerEmail for production)


Tool 3: EndWorkflow

  • Purpose: cleanly terminate with Succeeded once done

📊 Run History

One of the most useful ways to validate and debug your Logic App is by looking at the Run History. Each execution shows:

  • Workflow steps on the left: trigger, RefundAgent, and tools (e.g., Send Email for Action, Send Final Status Email).
  • Agent log on the right: detailed reasoning steps, the request payload, and the deterministic JSON decision generated by the Agent.
  • Status indicators ✅ or ❌ that help you quickly spot whether a step succeeded or failed.

In the RefundAgent example:

📸 Screenshot: Run History — left side shows the Logic App workflow steps, right side shows the Agent log with reasoning and decision.

  • For a high-value refund ($275.50), the Agent matched a valid reason (“defective”) but since the amount exceeded $200, it escalated to a human approver. The run shows the “Send Email for Approval or Denial” step being called.
  • For a lower-value refund ($145.00), the Agent saw that the reason (“item damaged”) was valid and the amount was below the threshold, so it auto-approved the refund and sent the final status email directly to the customer.

This gives full transparency into how the decision was made and which path the workflow followed — critical for auditability in HITL (Human-in-the-Loop) scenarios.


Wrap-up

You now have a deterministic, auditable refund flow that mixes
policy automation with human judgement --- all inside Logic

Apps.

Top comments (0)