Your agent is about to wire $50,000. Who approves it?
You built the agent. You wired up LangGraph's interrupt() to pause before the irreversible action. Now what? Who gets notified? Where do they decide? What if they're on holiday? You're three days from launch and you're about to build a notification system, a dashboard, and an escalation engine from scratch.
Wire $50,000 to a new vendor account
The agent has verified the details. It needs one human to confirm before it moves the money. With LangGraph alone — nobody knows it's waiting.
Send rejection emails to 40 candidates
The pipeline ran overnight. Before it fires 40 emails, someone on the team should review the shortlist. interrupt() paused it. But who got pinged?
"When a thread is interrupted, nobody gets notified. No email, no Slack, no ping of any kind."
"There's no built-in mechanism to say 'if nobody responds in 30 minutes, escalate to the backup approver.'"
Approval Hub fills every gap — deployed in 5 minutes, free forever.
Dashboard · Email · Slack · Audit trail · Escalation · Team routing. Open source, self-hosted. And you're not alone — 85% of teams building AI agents say human oversight is non-negotiable (Gartner, 2025).
Up and running in 5 minutes
Four steps. One Vercel deploy, one env var table, one pip install, three lines of Python.
Deploy your hub to Vercel
One click. Vercel clones the repo and prompts you for env vars during setup.
▲ Deploy to VercelSet environment variables
In Vercel → Project Settings → Environment Variables, add:
| Variable | Where to get it |
|---|---|
SUPABASE_URL | Supabase → Project Settings → API |
SUPABASE_SERVICE_ROLE_KEY | Supabase → Project Settings → API → service_role |
SUPABASE_ANON_KEY | Supabase → Project Settings → API → anon/public |
API_SECRET_TOKEN | Any random string — becomes your auth key |
NEXT_PUBLIC_APP_URL | Your Vercel deployment URL (e.g. https://my-hub.vercel.app) |
RESEND_API_KEY | resend.com → API Keys (optional — enables email) |
SLACK_WEBHOOK_URL | Slack → Incoming Webhooks (optional — enables Slack) |
Install the SDK
pip install langgraph-approval-hubrequests — no LangChain version pinning, no conflicts.Add to your agent
Three lines. Paste anywhere inside a LangGraph node function.
from langgraph_approval_hub import request_approval
decision = request_approval(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
agent_name="Finance Agent",
action_description="Process $4,200 refund for 12 customers",
assignee="alice@acme.com",
assignee_type="email",
)
if decision == "approved":
process_refunds()request_approval() blocks until a human decides. Alice gets an email with a direct dashboard link. Your agent resumes the moment she clicks Approve or Reject.Real-world examples
Every team has slightly different requirements. These patterns cover the most common ones.
Basic approval — person assignee
The simplest case: one person must approve before the agent continues.
from langgraph_approval_hub import request_approval
decision = request_approval(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
agent_name="Finance Agent",
action_description="Process $4,200 refund for 12 customers",
assignee="alice@acme.com",
assignee_type="email",
)
if decision == "approved":
process_refunds()Route to a team
Use assignee_type="team" when multiple people share responsibility. All team members are notified — first to respond wins.
decision = request_approval(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
agent_name="Data Pipeline Agent",
action_description="Drop and recreate the events_staging table",
assignee="data-team", # team name from Settings → Teams
assignee_type="team",
)Escalate if no response
If the assignee doesn't respond within timeout_minutes, the hub re-notifies the escalation target and marks the request as Escalated on the dashboard.
decision = request_approval(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
agent_name="Billing Agent",
action_description="Issue $12,000 credit to enterprise account ACME-001",
assignee="billing@acme.com",
assignee_type="email",
escalate_to="cfo@acme.com", # notified if no response
timeout_minutes=30, # escalates after 30 min
)Show agent reasoning + handle errors
Pass agent_reasoning to give the approver full context. Always wrap in try/except — the SDK raises TimeoutError and RuntimeError.
decision = request_approval(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
agent_name="Customer Service Agent",
action_description="Issue full refund of $340 to customer #A-4821",
assignee="support-team",
assignee_type="team",
agent_reasoning="""
1. Customer purchased on 2024-03-01, within the 30-day return window.
2. Item returned in original packaging — no restocking fee applies.
3. Customer account has no prior refund requests.
4. Refund amount matches the original charge exactly.
""",
)try:
decision = request_approval(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
agent_name="Finance Agent",
action_description="Wire $50,000 to vendor account",
assignee="finance-team",
assignee_type="team",
timeout_minutes=60,
)
except TimeoutError:
# Nobody responded — fail safe, do nothing
notify_team("Approval timed out — no action taken")
return
except RuntimeError as e:
# Hub unreachable or rejected the request
log_error(e)
raise
if decision == "approved":
execute_wire_transfer()decision == "rejected" or an exception is raised, your agent should do nothing. Never treat a missing decision as implicit approval.Non-blocking (async) pattern
Use submit_approval() when you want to continue working while waiting for a decision. Check back later with get_decision().
from langgraph_approval_hub import submit_approval, get_decision
# Submit — returns immediately, agent continues
approval_id = submit_approval(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
agent_name="OutreachBot",
action_description="Send cold email campaign to 200 leads",
assignee="marketing@company.com",
assignee_type="email",
)
# ... do other work while waiting ...
# Check the decision later (non-blocking)
result = get_decision(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
approval_id=approval_id,
)
print(result["status"]) # "pending" | "approved" | "rejected" | "expired"List all pending approvals:
from langgraph_approval_hub import get_pending
pending = get_pending(
hub_url="https://your-hub.vercel.app",
api_token="your-api-secret-token",
)
for item in pending:
print(item["id"], item["agent_name"], item["status"])pip install langgraph-approval-hub==0.2.0Deploy for your whole org
One hub instance handles all your agents. Route approvals to the right team, escalate to managers, export decisions for compliance.
How it fits together
Enabling notifications
Set these two env vars in Vercel — both are free:
| Variable | Service | What it enables |
|---|---|---|
RESEND_API_KEY | resend.com — free up to 100 emails/day | Email to assignee (and team members) on every new request |
SLACK_WEBHOOK_URL | Slack Incoming Webhooks — free | Slack message to your channel on every new request + escalation |
Audit trail
Every decision is recorded — agent name, action, who decided, when, and any note they left. Go to the Audit Log to view or export as JSON.
↗ Export JSON for compliance exports.SDK & API reference
Full parameter list and API endpoint table.
SDK parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
hub_url | str | Required | Your Approval Hub URL |
api_token | str | Required | Your API_SECRET_TOKEN env var value |
agent_name | str | Required | Name shown in the dashboard |
action_description | str | Required | Plain-English description of what the agent wants to do |
assignee | str | Required | Email address or team name |
assignee_type | "email" | "team" | Required | Whether assignee is an individual or a team |
agent_reasoning | str | Optional | Step-by-step reasoning shown to the approver on the detail page |
escalate_to | str | Optional | Email to escalate to if timeout exceeded |
timeout_minutes | int | Optional | Minutes before escalation triggers (default: 60) |
poll_interval | int | Optional | Seconds between status polls — request_approval() only (default: 5) |
API endpoints
All endpoints are on your Vercel deployment URL. POST /api/interrupt requires a Bearer token in the Authorization header.
| Method | Route | Auth | Description |
|---|---|---|---|
POST | /api/interrupt | Bearer token | Create approval request, fire notifications |
GET | /api/approvals | Bearer token | List approvals (?status=pending|escalated) |
GET | /api/approvals/[id] | Bearer token | Get single approval with notification log |
POST | /api/approvals/[id]/decide | None | Submit approve or reject decision |
GET | /api/audit | None | Full audit log — also used for JSON export |
GET | /api/teams | None | List all configured teams |
POST | /api/teams | None | Create or update a team |