Use Case · Customer service · Signed Webhook
A customer service approval gated through a signed webhook
How a webhook-triggered support agent handles this end to end — a routine question answered immediately, a refund request paused so a human reviews the drafted reply before anything goes out.
A new email triggers the agent
A customer email comes in. The request hits POST /messages — not the streaming route, nobody's watching this exact response. The agent looks up the order first (lookup_order, auto-allowed) to see what it's actually dealing with.
A routine question gets answered right away
If it's an ordinary question — where's my package, what's the status — send_email's own intent field comes back "tracking_info". That matches an auto-allow rule: the reply goes straight out, no human in the loop for this branch.
A refund request pauses for approval instead
If the customer asks for a refund, the agent drafts a refund reply and calls send_email with intent: "refund" — that falls through to an "ask" rule instead of the auto-allow above. The call returns instantly with stopReason: "pending_approval" and a pendingId; a signed webhook (HMAC-SHA256, X-Actauth-Signature) fires to the configured destination. The process is free to exit — nothing is held open.
A human reviews the drafted reply — and can edit it
Whenever they get to it: read what the agent actually drafted, and either approve it as-is or fix the wording first. POST /pending-approvals/:id/resolve takes { "decision": "approve", "editedArgs": { ... } } — editedArgs is optional, and when it's given, it's what actually gets sent, not the model's original draft.
Approved: the reply actually sends
The turn resumes from its durable checkpoint and send_email runs for real, with the edited body if there was one — the customer gets whatever the human actually signed off on.
Cascade-skip, the other real case
If two calls in the same batch are both pending and one gets denied, the checkpoint closes immediately — the denial doesn't wait for its sibling. The still-outstanding item gets a synthesized skipped result instead of being left dangling, and the turn resumes with both outcomes accounted for in one message.
Same mechanism, a different domain: see the incident-triage use case for on-call approvals gated through Slack's own interactive buttons, or the expense-approval use case for a channel with no platform signature to lean on at all.