Can Agents Run It? · evidence — Issue 1: Supabase

claude-sonnet-5 · d2_webhooks · rep 2

pass
model
claude-sonnet-5
task
d2_webhooks
rep
2
day
2
steps
15
tokens
67,013
est. cost
$0.402
agent time
81s
wave
s4
started
2026-07-24T05:08:21Z

The task as the agent received it

You have operator access to a hosted Supabase project. Credentials are in environment variables: SUPABASE_URL, SUPABASE_PROJECT_REF, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_DB_URL, SUPABASE_DB_PASSWORD. Every INSERT into the existing table public.signups must deliver an HTTP webhook: a POST request with a JSON body containing at least the new row's email and plan values, sent to the receiver endpoint {SUPABASE_URL}/functions/v1/hook-recorder (an edge function that is already deployed on this project; SUPABASE_URL is in your environment). The receiver requires a valid project JWT in the Authorization header and records every payload it accepts. Implement the delivery from the database itself, so it fires for ANY insert into signups no matter which client performs it — no external workers, no cron, no client-side code. Delivery may be asynchronous (a few seconds is fine). Prove it end-to-end with a test insert before finishing: the receiver stores accepted payloads in public.webhook_receipts.

What it ran — 15 commands

#1exit 0
env | grep -i supabase | sed 's/=.*KEY.*/=[REDACTED]/' | sed 's/PASSWORD.*/=[REDACTED]/'
SUPABASE_DB_=[REDACTED]
SUPABASE_DB_URL=<redacted:SUPABASE_DB_URL>
SUPABASE_URL=https://<ref>.supabase.co
SUPABASE_SERVICE_ROLE_KEY=<redacted:SUPABASE_SERVICE_ROLE_KEY>
SUPABASE_PROJECT_REF=<ref>
SUPABASE_ANON_KEY=<redacted:SUPABASE_ANON_KEY>
#2exit 0
which psql curl jq 2>&1; psql --version 2>&1
/usr/bin/psql
/usr/bin/curl
/usr/bin/jq
psql (PostgreSQL) 18.4 (Debian 18.4-1.pgdg12+1)
#3exit 0
export PGCONNECT_TIMEOUT=10
psql "$SUPABASE_DB_URL" -c "\d public.signups"
echo "---extensions---"
psql "$SUPABASE_DB_URL" -c "select extname from pg_extension;"
echo "---check pg_net---"
psql "$SUPABASE_DB_URL" -c "select * from pg_available_extensions where name in ('pg_net','http') ;"
                                     Table "public.signups"
   Column   |           Type           | Collation | Nullable |             Default              
------------+--------------------------+-----------+----------+----------------------------------
 id         | bigint                   |           | not null | generated by default as identity
 email      | text                     |           | not null | 
 plan       | text                     |           | not null | 'free'::text
 created_at | timestamp with time zone |           | not null | now()
Indexes:
    "signups_pkey" PRIMARY KEY, btree (id)

---extensions---
      extname       
--------------------
 plpgsql
 pg_stat_statements
 uuid-ossp
 pgcrypto
 supabase_vault
(5 rows)

---check pg_net---
  name  | default_version | installed_version |                                  comment                                   
--------+-----------------+-------------------+----------------------------------------------------------------------------
 http   | 1.6             |                   | HTTP client for PostgreSQL, allows web page retrieval inside the database.
 pg_net | 0.20.4          |                   | Async HTTP
(2 rows)

#4exit 0
curl -s -o /dev/null -w "no auth status: %{http_code}\n" -X POST "$SUPABASE_URL/functions/v1/hook-recorder" -H "Content-Type: application/json" -d '{"email":"test@example.com","plan":"free"}'
curl -s -o /dev/null -w "anon auth status: %{http_code}\n" -X POST "$SUPABASE_URL/functions/v1/hook-recorder" -H "Content-Type: application/json" -H "Authorization: Bearer $SUPABASE_ANON_KEY" -d '{"email":"test@example.com","plan":"free"}'
echo "---"
psql "$SUPABASE_DB_URL" -c "select table_name from information_schema.tables where table_schema='public' and table_name='webhook_receipts';"
psql "$SUPABASE_DB_URL" -c "\d public.webhook_receipts"
no auth status: 401
anon auth status: 200
---
    table_name    
------------------
 webhook_receipts
(1 row)

                                 Table "public.webhook_receipts"
   Column    |           Type           | Collation | Nullable |             Default              
-------------+--------------------------+-----------+----------+----------------------------------
 id          | bigint                   |           | not null | generated by default as identity
 received_at | timestamp with time zone |           | not null | now()
 payload     | jsonb                    |           | not null | 
Indexes:
    "webhook_receipts_pkey" PRIMARY KEY, btree (id)

#5exit 0
psql "$SUPABASE_DB_URL" -c "select * from public.webhook_receipts order by id desc limit 5;"
 id |          received_at          |                    payload                    
----+-------------------------------+-----------------------------------------------
  2 | 2026-07-24 05:08:48.245441+00 | {"plan": "free", "email": "test@example.com"}
(1 row)

#6exit 0
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 <<'EOF'
create extension if not exists pg_net with schema extensions;
EOF
CREATE EXTENSION
#7exit 0
psql "$SUPABASE_DB_URL" -c "select schemaname, extname, extnamespace::regnamespace from pg_extension where extname='pg_net';"
psql "$SUPABASE_DB_URL" -c "\df extensions.net.*" 2>&1 | head -20
psql "$SUPABASE_DB_URL" -c "select proname, pronamespace::regnamespace from pg_proc where proname like 'http_%';"
cross-database references are not implemented: extensions.net.*
        proname        | pronamespace 
-----------------------+--------------
 http_collect_response | net
 http_delete           | net
 http_get              | net
 http_post             | net
(4 rows)

ERROR:  column "schemaname" does not exist
LINE 1: select schemaname, extname, extnamespace::regnamespace from ...
               ^
#8exit 0
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 <<'EOF'
-- Store the webhook URL and auth token securely in Supabase Vault
select vault.create_secret(
  'https://<ref>.supabase.co/functions/v1/hook-recorder',
  'hook_recorder_url',
  'URL for the signups insert webhook receiver'
);
EOF
            create_secret             
--------------------------------------
 ad024eeb-2c29-485d-ac47-c33b142c3814
(1 row)

#9exit 0
cat <<EOF > /tmp/store_token.sql
select vault.create_secret(
  '${SUPABASE_ANON_KEY}',
  'hook_recorder_token',
  'Bearer token (project JWT) for hook-recorder edge function'
);
EOF
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -f /tmp/store_token.sql
shred -u /tmp/store_token.sql
            create_secret             
--------------------------------------
 f22712e1-2021-45d3-9207-d38f9e5abc20
(1 row)

#10exit 0
psql "$SUPABASE_DB_URL" -c "select name, decrypted_secret from vault.decrypted_secrets where name in ('hook_recorder_url','hook_recorder_token');"
        name         |                                                                                                 decrypted_secret                                                                                                 
---------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 hook_recorder_url   | https://<ref>.supabase.co/functions/v1/hook-recorder
 hook_recorder_token | <redacted:SUPABASE_ANON_KEY>
(2 rows)

#11exit 0
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 <<'EOF'
create or replace function public.notify_signup_webhook()
returns trigger
language plpgsql
security definer
set search_path = public, extensions, vault, net
as $$
declare
  v_url   text;
  v_token text;
  v_request_id bigint;
begin
  select decrypted_secret into v_url
    from vault.decrypted_secrets where name = 'hook_recorder_url';
  select decrypted_secret into v_token
    from vault.decrypted_secrets where name = 'hook_recorder_token';

  if v_url is null or v_token is null then
    raise warning 'hook_recorder_url/token not configured in vault; skipping webhook';
    return new;
  end if;

  select net.http_post(
    url     := v_url,
    headers := jsonb_build_object(
                 'Content-Type', 'application/json',
                 'Authorization', 'Bearer ' || v_token
               ),
    body    := jsonb_build_object(
                 'email', new.email,
                 'plan', new.plan,
                 'id', new.id,
                 'created_at', new.created_at
               )
  ) into v_request_id;

  return new;
end;
$$;

drop trigger if exists trg_signups_webhook on public.signups;

create trigger trg_signups_webhook
after insert on public.signups
for each row
execute function public.notify_signup_webhook();
EOF
CREATE FUNCTION
DROP TRIGGER
CREATE TRIGGER
NOTICE:  trigger "trg_signups_webhook" for relation "public.signups" does not exist, skipping
#12exit 0
psql "$SUPABASE_DB_URL" -c "select count(*) as before_count from public.webhook_receipts;"
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -c "insert into public.signups (email, plan) values ('e2e-test-$(date +%s)@example.com', 'enterprise') returning *;"
 before_count 
--------------
            1
(1 row)

 id |              email              |    plan    |          created_at           
----+---------------------------------+------------+-------------------------------
  1 | e2e-test-1784869764@example.com | enterprise | 2026-07-24 05:09:24.896412+00
(1 row)

INSERT 0 1
#13exit 0
sleep 5
psql "$SUPABASE_DB_URL" -c "select * from public.webhook_receipts order by id desc limit 5;"
echo "--- pg_net response status check ---"
psql "$SUPABASE_DB_URL" -c "select id, status_code, created, (content::text) from net._http_response order by id desc limit 3;"
 id |          received_at          |                                                            payload                                                            
----+-------------------------------+-------------------------------------------------------------------------------------------------------------------------------
  3 | 2026-07-24 05:09:25.076571+00 | {"id": 1, "plan": "enterprise", "email": "e2e-test-1784869764@example.com", "created_at": "2026-07-24T05:09:24.896412+00:00"}
  2 | 2026-07-24 05:08:48.245441+00 | {"plan": "free", "email": "test@example.com"}
(2 rows)

--- pg_net response status check ---
 id | status_code |            created            |   content   
----+-------------+-------------------------------+-------------
  1 |         200 | 2026-07-24 05:09:24.900311+00 | {"ok":true}
(1 row)

#14exit 0
# Insert via PostgREST REST API using anon key (simulating an external client), assuming RLS allows or none set
psql "$SUPABASE_DB_URL" -c "select relrowsecurity from pg_class where relname='signups';"
curl -s -w "\nHTTP_STATUS:%{http_code}\n" -X POST "$SUPABASE_URL/rest/v1/signups" \
  -H "apikey: $SUPABASE_ANON_KEY" \
  -H "Authorization: Bearer $SUPABASE_ANON_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: return=representation" \
  -d '{"email":"rest-client-test@example.com","plan":"pro"}'
 relrowsecurity 
----------------
 f
(1 row)

[{"id":2,"email":"rest-client-test@example.com","plan":"pro","created_at":"2026-07-24T05:09:39.852569+00:00"}]
HTTP_STATUS:201
#15exit 0
sleep 5
psql "$SUPABASE_DB_URL" -c "select * from public.webhook_receipts order by id desc limit 5;"
 id |          received_at          |                                                            payload                                                            
----+-------------------------------+-------------------------------------------------------------------------------------------------------------------------------
  4 | 2026-07-24 05:09:40.007252+00 | {"id": 2, "plan": "pro", "email": "rest-client-test@example.com", "created_at": "2026-07-24T05:09:39.852569+00:00"}
  3 | 2026-07-24 05:09:25.076571+00 | {"id": 1, "plan": "enterprise", "email": "e2e-test-1784869764@example.com", "created_at": "2026-07-24T05:09:24.896412+00:00"}
  2 | 2026-07-24 05:08:48.245441+00 | {"plan": "free", "email": "test@example.com"}
(3 rows)

What it said at the end

Confirmed end-to-end for a real external client (via the REST API using only the anon key, no server-side code involved) — the trigger fired automatically and the payload was recorded. ## Summary I implemented database-native webhook delivery for every insert into `public.signups`, entirely inside Postgres (no external workers/cron/client code): 1. **Enabled `pg_net`** (`create extension pg_net`) — Supabase's async HTTP client that runs inside Postgres via a background worker. 2. **Stored secrets in Supabase Vault** (`vault.create_secret`) rather than hardcoding them: - `hook_recorder_url` → `{SUPABASE_URL}/functions/v1/hook-recorder` - `hook_recorder_token` → a valid project JWT (anon key) for the `Authorization: Bearer` header required by the receiver. 3. **Created a trigger function** `public.notify_signup_webhook()` (`SECURITY DEFINER`) that reads those secrets from `vault.decrypted_secrets` and calls `net.http_post(...)` with a JSON body containing `email`, `plan`, `id`, and `created_at` from the new row. The call is async (queued to `net._http_response`), so the insert itself isn't blocked. 4. **Attached an `AFTER INSERT` trigger** (`trg_signups_webhook`) on `public.signups` calling that function for every row — this fires regardless of which client/role performs the insert (psql, REST API, service role, etc.), since it lives at the table level in the database. 5. **Verified end-to-end twice**: - Direct SQL insert (`psql`) → webhook delivered, `net._http_response` shows `status_code 200`, row appeared in `public.webhook_receipts` with correct `email`/`plan`. - Insert via the actual Supabase REST API (`POST /rest/v1/signups` with only the anon key, simulating a real external client) → webhook also delivered automatically and recorded in `webhook_receipts`, proving the trigger fires for any insert path with no reliance on client code.

12 earlier narration turn(s) are in the raw transcript.

How it was graded

A shell script executed against the live project and asserted on the state the agent left behind. No model judged another model.

exit 0 OK

Check it yourself

Raw transcript · Task definition and verifier · Every attempt