The problem
An AI agent that calls Stripe directly can issue a refund the moment it decides to — no review, no record, no recourse. One hallucination or misread order ID and the money is gone.
// ❌ Direct Stripe call — executes immediately, no audit trail
await stripe.refunds.create({ charge: "ch_3Qx9R2eZpVMlhbBV0Abc123", amount: 4999 });
The SafeFetch solution
Wrap the Stripe call in safeFetch() with approve: true. The refund is held as awaiting_approval until a human clicks approve. Nothing hits Stripe until that happens.
Full example
import { safeFetch } from "safefetch";
const action = await safeFetch({
url: "https://api.stripe.com/v1/refunds",
method: "POST",
headers: {
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: {
charge: "ch_3Qx9R2eZpVMlhbBV0Abc123",
amount: 4999, // $49.99 in cents
reason: "requested_by_customer",
},
approve: true,
// Optional: email a reviewer a magic-link approve/reject button
notify: { email: "billing-team@yourcompany.com" },
// Optional: notify your backend when the refund completes
callback: "https://yourapp.com/webhooks/safefetch",
});
console.log(action.id); // act_...
console.log(action.status); // "awaiting_approval"
The action is created immediately and returns with status: "awaiting_approval". Stripe is not contacted yet.
What happens next
Reviewer receives email
If you passed notify.email, SafeFetch sends an email with one-click Approve and Reject buttons. No login required — the links are signed magic links.
Reviewer approves
Clicking Approve moves the action to pending and dispatches the request to Stripe. The receipt page shows the full action details, including the Stripe response.
Your callback fires
If you passed a callback URL, SafeFetch sends a POST to it when the action reaches a terminal status (completed or failed). Check action.status and action.response_code to confirm the refund went through.
Checking status programmatically
Poll or check status at any point using the action ID:
const action = await safeFetch.get("act_Xk9mP2nQ4rT1vW8sY");
if (action.status === "awaiting_approval") {
console.log("Still waiting for reviewer sign-off.");
} else if (action.status === "completed") {
console.log("Refund issued. Stripe responded:", action.response_code);
// action.response_body contains the full Stripe refund object
} else if (action.status === "failed") {
console.log("Refund failed:", action.last_error);
}
Approving via API
If you have your own approval UI instead of the magic-link email, approve programmatically:
await safeFetch.approve("act_Xk9mP2nQ4rT1vW8sY");
Or cancel (reject) it:
await safeFetch.cancel("act_Xk9mP2nQ4rT1vW8sY");
Handling the callback
SafeFetch POSTs to your callback URL when the action finishes. Verify the signature and act on the result:
import type { Action } from "safefetch";
// In your webhook handler (e.g., Express, Hono):
app.post("/webhooks/safefetch", async (req, res) => {
const action: Action = req.body;
if (action.status === "completed" && action.response_code === 200) {
// Stripe accepted the refund — update your records
await db.orders.update({ id: action.body?.charge }, { refunded: true });
} else if (action.status === "failed") {
await alertTeam(`Refund failed for ${action.body?.charge}: ${action.last_error}`);
}
res.sendStatus(200);
});
Receipt page
Every action has a receipt page in the dashboard showing the full audit trail: who approved it, when, what Stripe returned, and how long delivery took. Share the URL with your finance team as documentation.
https://api.safefetch.dev/dashboard/actions/act_Xk9mP2nQ4rT1vW8sY
approve: true cannot be combined with sync: true. Synchronous mode waits for immediate completion — that’s incompatible with an asynchronous human decision.