Approval Gateway · Free tool
Add a human approval step to any workflow
A hosted human-approval step for any automation: one API call out, a signed decision back.
- One call in. POST a title and a callback URL.
- A human decides. A clean mobile page. Approve or reject in one tap.
- Your workflow hears back. Signed decision to your callback. Expires cleanly after 7 days.
Free API key, delivered by email
- 100 approvals a month, free
- No credit card
- Key in your inbox in ~30 seconds
Pick your platform
- Create a Zap that starts with Webhooks by Zapier → Catch Hook and copy its hook URL. This is where the decision will arrive.
-
In your main Zap, add an action step: Webhooks by Zapier → Custom Request,
filled in with:
Method
POSTURLhttps://rills.ai/api/tools/approval-gateway/approvalsHeadersAuthorization: Bearer apg_YOUR_KEYContent-Type: application/jsonReplace
apg_YOUR_KEYwith the key from your email. -
Paste this into Data, swap in your catch hook URL from step 1, and
change the title and message to match your workflow:
{ "title": "Refund $420 to Acme Corp", "message": "Order #1042 - customer reports a duplicate charge.", "callback_url": "https://hooks.zapier.com/hooks/catch/your-catch-hook", "metadata": { "order_id": "1042" } } -
Add a step that sends
approval_url(from the Custom Request's response) to whoever decides: Slack, email, SMS. They'll see a simple approve / reject page. -
When they decide, your catch hook Zap runs with the decision:
{ "id": "Vt2jW9qX...", "status": "approved", "decided_at": "2026-06-12T15:04:05.000Z", "comment": "Verified the duplicate charge - go ahead.", "metadata": { "order_id": "1042" } }
On a plan without premium webhooks? Skip the catch hook and check the status with a GET
request to https://rills.ai/api/tools/approval-gateway/approvals/<id> instead (same Authorization header).
Full walkthrough with screenshots: adding a human approval step in Zapier.
- In a new scenario, add Webhooks → Custom webhook and copy its address. This is where the decision will arrive.
-
In your main scenario, add an HTTP → Make a request module, filled in with:
Method
POSTURLhttps://rills.ai/api/tools/approval-gateway/approvalsHeadersAuthorization: Bearer apg_YOUR_KEYReplace
apg_YOUR_KEYwith the key from your email. -
Set the body type to Raw (JSON), paste this, and swap in your webhook
address from step 1:
{ "title": "Refund $420 to Acme Corp", "message": "Order #1042 - customer reports a duplicate charge.", "callback_url": "https://hook.us1.make.com/your-custom-webhook", "metadata": { "order_id": "1042" } } -
Use
approval_urlfrom the module's response in an email or Slack module. Whoever opens it sees a simple approve / reject page. -
When they decide, your webhook scenario runs with the decision:
{ "id": "Vt2jW9qX...", "status": "approved", "decided_at": "2026-06-12T15:04:05.000Z", "comment": "Verified the duplicate charge - go ahead.", "metadata": { "order_id": "1042" } }
Full walkthrough with screenshots: human approval steps in Make.
-
Add an HTTP Request node, filled in with:
Method
POSTURLhttps://rills.ai/api/tools/approval-gateway/approvalsHeadersAuthorization: Bearer apg_YOUR_KEYReplace
apg_YOUR_KEYwith the key from your email. -
Use this JSON body. The
callback_urlexpression points at the Wait node's resume URL, so n8n fills it in for you:{ "title": "Refund $420 to Acme Corp", "message": "Order #1042 - customer reports a duplicate charge.", "callback_url": "{{ $execution.resumeUrl }}", "metadata": { "order_id": "1042" } } - Add a Wait node right after, set to On webhook call. Your workflow sleeps at zero cost until the decision lands.
-
Send
approval_url(from the HTTP Request node's response) with a Slack or email node. When the human decides, the workflow resumes with:{ "id": "Vt2jW9qX...", "status": "approved", "decided_at": "2026-06-12T15:04:05.000Z", "comment": "Verified the duplicate charge - go ahead.", "metadata": { "order_id": "1042" } }
Full walkthrough with screenshots: human approval workflows in n8n.
If it can send an HTTPS request and receive one back, it can use the gateway. One request
creates the approval; the decision arrives at your callback_url.
Create an approval
curl -X POST https://rills.ai/api/tools/approval-gateway/approvals \
-H "Authorization: Bearer apg_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Refund $420 to Acme Corp",
"message": "Order #1042 - customer reports a duplicate charge.",
"callback_url": "https://your-app.example.com/webhooks/approval",
"metadata": { "order_id": "1042" }
}' Response
{
"id": "Vt2jW9qX...",
"approval_url": "https://rills.ai/approve/Vt2jW9qX...",
"status": "pending",
"expires_at": "2026-06-19T14:00:00.000Z"
} Your callback receives
{
"id": "Vt2jW9qX...",
"status": "approved",
"decided_at": "2026-06-12T15:04:05.000Z",
"comment": "Verified the duplicate charge - go ahead.",
"metadata": { "order_id": "1042" }
} No webhook? Poll the status
curl https://rills.ai/api/tools/approval-gateway/approvals/Vt2jW9qX... \
-H "Authorization: Bearer apg_YOUR_KEY" {
"id": "Vt2jW9qX...",
"status": "pending",
"decided_at": null,
"comment": null,
"expires_at": "2026-06-19T14:00:00.000Z"
} Verify the signature (optional)
The unguessable callback URL is your baseline protection. Verify signatures when your
callback is code you run yourself. Callbacks carry X-Rills-Signature
(HMAC-SHA256 of timestamp.body, keyed by your signing secret) and
X-Rills-Timestamp.
const crypto = require("node:crypto");
function verifyRillsSignature(rawBody, headers, signingSecret) {
const timestamp = headers["x-rills-timestamp"];
// Reject stale callbacks (5 minute tolerance) to prevent replay.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
"sha256=" +
crypto
.createHmac("sha256", signingSecret)
.update(timestamp + "." + rawBody)
.digest("hex");
const provided = headers["x-rills-signature"] ?? "";
return (
provided.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected))
);
} Frequently asked questions
What does the Approval Gateway cost?
Nothing. Every key includes 100 approvals per month, delivered by email. Human approvals are free in Rills itself too; it's a core part of how we price.
How does my workflow find out about the decision?
When the approver decides (or the request expires after 7 days), we POST a JSON payload to your callback_url with the status, comment, and your echoed metadata. Every callback is signed with HMAC-SHA256 so you can verify it came from us. Tools without a webhook trigger can poll the status endpoint instead.
What happens if nobody approves?
Approvals stay open for 7 days. After that we mark them expired and fire your callback with status "expired", so your workflow can take a fallback path instead of hanging forever.
Is the approval link secure?
The link contains an unguessable 128-bit-plus random token, the same capability-URL model Zapier and n8n use for resume URLs. Anyone with the link can decide, so treat it like the approval itself: send it to the right person. Titles and messages render as plain text, never as HTML or links.
Can approvers get push notifications instead of a web page?
That's the full Rills product: the same approvals land in a mobile approval queue with push notifications, swipe-to-approve, audit history, and confidence gates that learn which approvals can run themselves. The gateway is the miniature version.
Want approvals that learn?
The gateway asks a human every time, forever. Rills workflows learn from your approvals and graduate to running themselves.