AgeBridge Guided Project · AdvancedAbout 14 hoursPro

Approval inbox with an audit log

A service any of your other builds can call when a person has to say yes: it posts the request to Slack with Approve and Reject controls, calls the caller back with the decision, and keeps a row saying who decided what and when.

What you'll have at the end

A repository and a public Vercel address with four endpoints, a Supabase table of requests, a Slack channel where each pending action arrives with two controls, an audit table with one row per decision, a job that expires anything unanswered after 24 hours, and a one-page inbox.

What it proves

human in the loopsigned webhooksaudit trail

Accounts to create

  • Instead of Claude Code: Codex

What it costs: Free tiers on Supabase, Vercel, GitHub and Slack. No model usage at all: nothing in this build calls a model.

Have this ready

  • A Slack workspace where you are allowed to create an app and a channel
  • A terminal with Node 20.6 or newer, git, and Claude Code installed; Codex works the same way, with the same prompts
  • A GitHub account where you may create a repository
  • One made-up decision a business would want a person to approve, in one line; the copy blocks use a 240-shekel refund, so swap that for your own

How it fits together

POST /api/requestsanother build asks for a decisionrequests tableone row, status pendingSlack #approvalsApprove button, reject menu/api/slack-actionssignature checked, row updatedapproval_auditwho, when, what, whycallback_urlthe caller hears the answer/api/expire24 hours with no answer
  1. POST /api/requests: another build asks for a decision
  2. requests table: one row, status pending
  3. Slack #approvals: Approve button, reject menu
  4. /api/slack-actions: signature checked, row updated
  5. approval_audit: who, when, what, why
  6. callback_url: the caller hears the answer
  7. /api/expire: 24 hours with no answer

The steps

  1. 1. The table and the repository

    One table that holds pending decisions, and a repository holding the service that will fill it. After this step nothing is live, but the shape of every row and every endpoint is decided.

  2. 2. A public address that stores a request

    The endpoint live on an address Slack will be able to reach later. Until the service has a public address, no button anywhere can call it back.

  3. 3. The request lands in Slack

    The part a person sees. Every pending row arrives in one channel with two controls, so nobody has to open a database to say yes.

  4. 4. The decision, checked and recorded

    The endpoint Slack calls when somebody presses a control. It proves the call really came from Slack, updates the row, tells the original caller, and replaces the message so nobody decides twice.

  5. 5. The audit table

    The row the client actually cares about six months later: who decided, when, what they decided and why. The requests table changes; the audit table only ever grows.

  6. 6. Anything unanswered expires

    The quiet failure this kind of service has: a request nobody ever presses. After this step it closes itself, tells the channel, and answers the caller, instead of waiting forever.

  7. 7. The inbox page and the hand-over

    One page that answers "what is waiting, and what did we decide?" without a database login, and the two notes that let somebody else run this after you.