THE SAFETY LAYER FOR AI AGENTS

Autonomous agents,
human-controlled.

Policies pause the risky action. A human approves, rejects, or edits it. Your agent resumes, and the audit log proves it.

the demo emails you a real approval · no account needed

the rogue refund, caught
$ node support-agent.js
agent intent: refund customer #4821
agent amount: $10,000.00 (expected ~$10)
confirm POST /v1/requests → req_8fk2 · PENDING
confirm agent paused · approval sent to finance@acme.com
 
human Sarah Chen edited payload: amount 10000 → 10.00
human APPROVED · webhook signed + delivered in 87ms
agent resumed → refunded $10.00 · audit log written ✓

The end-to-end flow, finished.
Nothing to design, host, or maintain.

01 · THE EMAIL

C

Confirm.dev

to sarah@acme.com

Confirm.dev · Approval required

SupportBot is requesting approval

Refund $10,000 to customer #4821

Review & Respond

02 · THE DECISION

Refund $10,000 to customer #4821

PENDING

What the agent did first

01 Read ticket #5521: "charged wrong"

02 Newest order is $10.00

"amount": 10000 10.00 · edited
Approve with editsReject

03 · THE RECORD

Audit log

Refund $10,000 to customer #4821

APPROVED
resolved bysarah@acme.comedited10000 → 10.00note"fixed the amount"webhookdelivered · 87ms

Pause, decide, resume.

The whole loop is one API call, a magic link, and a signed webhook. Around it: escalation routing, approver context, parked agent state, and an immutable audit trail.

REQUEST

The agent yields

Your code calls POST /v1/requests with the exact payload. The agent stops there.

DECIDE

A human rules on it

The approver gets a magic link. No account, no app. They approve, reject, or edit the payload first. Every decision is recorded.

RESUME

The webhook lands

We POST the verdict to your endpoint, HMAC-signed. Execute effectivePayload. Human edits win.

FULL CONTEXT

Decisions in seconds

Pass reasoning and recentActions and the approver sees the agent's step-by-step trail and its own explanation, right above the payload. No archaeology before a verdict.

STATE PARKING

Humans take hours. Fine.

Attach up to 256KB of serialized agent context as agentState. The webhook hands it back when the decision lands, and a fresh worker rehydrates. Nothing holds memory while the human thinks.

YOUR CLOCK

Silence is never consent

Set a workspace-wide default expiry or per-request ttlMinutes. Unanswered requests auto-expire, your agent gets the webhook, and the action never runs.

Enterprises don't fear agents.
They fear unsupervised ones.

One integration, every team's nightmare scenario:

GUARDRAILS

The query that never ran

An ops agent proposes dropping a “stale” table that billing still reads nightly. The DBA rejects it with a note and the agent halts.

  • Reject stops the action cold
  • Notes flow back through the webhook so agents can re-plan
  • Unanswered requests fail safe, not open
the drop table, rejected
$ node ops-agent.js --ticket=OPS-441
agent proposed fix: DROP TABLE user_sessions_old
agent target: production · rows: 2,147,201
confirm POST /v1/requests → req_4jx1 · PENDING
confirm agent paused · approval sent to dba@acme.com
 
human REJECTED · note: “billing still reads that table”
agent halted · no query executed
confirm rejection webhook delivered in 92ms ✓

INTEGRATION

Wrap once.
Any stack.

The SDK guards your whole tool layer with policies, fail-closed, so nothing slips through unwrapped. Prefer raw HTTPS? It's one POST with a bearer key. MCP agents get an approval tool with zero code. Resume on the webhook, or poll.

Full API reference →
import { guard } from "@confirm/sdk";

// Wrap the whole tool layer once. Policies decide what needs a human.
const tools = guard(agentTools, {
  policies: [
    { when: (t) => t.name.startsWith("delete_"), notify: "security@acme.com" },
    { when: (t) => t.name === "refund" && t.args.amount > 500, notify: "finance@acme.com" },
    { when: (t) => t.name === "search", decision: "allow" }, // read-only, never gated
  ],
  default: "require", // fail-closed: a tool nobody ruled for still needs a human
});

// On approval the tool runs with the human-edited payload.

WHY A SERVICE

A ✅ in Slack is not an approval system.
No identity. No edits. No expiry. No audit trail.

You need toConfirmThe bot you maintain forever
Catch what you forgotPolicies gate the whole tool layer, fail-closed, so a tool nobody wrapped still needs a humanHand-wrap every risky call and hope you didn't miss one
Route by stakesnotify: "group:finance" sends big refunds to finance and access grants to security, each member gets their own linkA bot, a channel map, and routing logic you maintain forever
Know who decidedNamed identity, timestamp, IP, and note on every decision"Someone reacted with a checkmark in #approvals"
Fix before it runsApprovers edit the JSON inline, the agent executes the edited versionCopy the payload into a thread and hope the agent picks it up
Nobody answersRequests expire on your TTL and notify your agent. Silence is never consentThe run hangs, or worse, a retry loop fires the action anyway
Resume with contextPark up to 256KB of serialized agent state with the request, get it back in the webhook hours laterBuild your own checkpoint store and wire it to the bot
Prove it to an auditorAppend-only log of requests, decisions, and delivery receiptsSearch the channel scroll-back and export screenshots

The ping is the easy 5%. The rest is a security product someone has to run forever. That's our job. Yours is one POST.

SECURITY

Built like the infrastructure it protects.

HASHED CREDENTIALS

API keys and magic-link tokens are stored as SHA-256 hashes. A database leak can't mint requests or approve them.

SIGNED WEBHOOKS

Every delivery carries an HMAC-SHA256 signature over a timestamped payload, with per-endpoint secrets.

256-BIT MAGIC LINKS

Approval tokens are 256 bits of entropy, single-purpose, and dead at the TTL. There is no password to phish.

APPEND-ONLY AUDIT

Requests, decisions, and delivery attempts are immutable rows. SOC2 evidence is a query.

IDENTITY ON EVERY DECISION

Approvals record the approver, timestamp, IP address, user agent, and note.

FAIL SAFE, NOT OPEN

Unanswered requests expire and notify your system. Silence never becomes consent.

The full model is documented, not marketed: read the security page.

Pricing

Per approval, not per seat. Approvers are always free.

HOBBY

$0forever

  • 100 approvals / month
  • Solo workspace
  • Email notifications
  • 1 webhook endpoint
  • 7-day audit log

PRO

$29per month

  • 5,000 approvals / month
  • $0.02 per extra approval
  • 10 team seats
  • Approval groups for escalation
  • Email + Slack notifications
  • 90-day audit log

BUSINESS

$200per month

  • 25,000 approvals / month
  • 25 team seats
  • Unlimited approval groups
  • 12-month audit log (SOC2 evidence window)
  • Priority support
  • Label policies & quorum, soon

ENTERPRISE

Customannual

  • Custom volume & seats
  • Teams approvals
  • SSO / SAML
  • Indefinite audit retention
  • Datadog & Splunk export, soon

Exact limits per plan are in the docs.

Questions, answered.

What happens if nobody approves in time?+

Every request has a TTL (default 24 hours, up to 7 days). When it elapses, the request becomes EXPIRED and a request.expired webhook fires. Your agent fails safe instead of hanging or acting anyway.

Do approvers need a Confirm account?+

No. Approvers get a single-purpose magic link by email, and the token in the link is the credential. Only developers who build with the API have accounts.

Can the approver change what the agent does?+

Yes. The approver fixes the JSON in the review screen and approves. The webhook's effectivePayload carries their version, and the original stays stored for the audit trail.

How do I know a webhook really came from you?+

Every delivery is signed: HMAC-SHA256 over timestamp.body with your endpoint's secret, sent in X-Confirm-Signature. Verification is about six lines, shown in the samples above.

What happens to my agent's context while a human takes hours?+

Pass it as agentState when you create the request: up to 256KB of serialized context, stored with the request and returned in the webhook. A fresh worker rehydrates from it and continues, so nothing holds memory while the human decides. Approvers never see this field.

Does it work with LangChain / Vercel AI SDK / CrewAI?+

Yes. Anywhere you can make an HTTPS request, you can gate an action: a LangChain tool, a Vercel AI SDK tool call, a CrewAI task, or a plain function.

What exactly is in the audit log?+

Every request, every decision (identity, timestamp, IP, note, payload edits), and every webhook delivery attempt with its HTTP status. Rows are append-only.

Isn't this what guardrails do?+

Guardrails are automated filters: they catch known patterns, without judgment. Confirm puts a human on the judgment calls, like the plausible-looking $10,000 refund or the deploy at the wrong moment. Run both. Guardrails for the obvious, a human approval for the irreversible.

Ship the agent. Keep the veto.

Your first hundred approvals are free. Integration is one POST request.