Files
Alexis Aguilar 8a78b7e5be docs: call Edge Functions from Postgres with the secret key (#51990)
Found during my dogfooding project :)

## Problem

Three guides give conflicting guidance on how Postgres should
authenticate when it calls an Edge Function:

- **Securing Edge Functions:** calls from `pg_net` and cron jobs send
the secret key on `apikey`, with `auth: 'secret'` and `verify_jwt =
false`. The Dashboard agrees: the webhook and cron job forms only offer
**Add secret key**.
- **Scheduling Edge Functions:** the only example that stores a key in
Vault sends the publishable key. A request with only the publishable key
passes the `verify_jwt` check, so anyone who has that public key can
call the function.
- **Database Webhooks:** doesn't cover calling an Edge Function or
authenticating to it. Its SQL example writes headers inline, so a
webhook created in a migration puts the secret key in a committed file.

Resolves [DOCS-1458](https://linear.app/supabase/issue/DOCS-1458),
[FDBKIN-19992](https://linear.app/supabase/issue/FDBKIN-19992)

## Solution

All three pages now recommend the secret key for calls from Postgres.

- **Scheduling Edge Functions:** stores and sends the secret key,
explains why the publishable key isn't safe here, and links to the
function-side setup.
- **Database Webhooks:** a new Call an Edge Function section covers the
Supabase Dashboard flow and a migration. The migration reads the key
from Vault in a `security definer` trigger function. The section
explains that the definer is required, because the roles that insert
rows can't read Vault.
- **Securing Edge Functions:** the service-to-service section is now a
two-step setup. It shows how to disable the `verify_jwt` check in
`supabase/config.toml`, with a link to the Authorization headers guide,
and how to use `auth: 'secret'` in the function. It then links to
Database Webhooks and Scheduling Edge Functions.
- **`pg_net` queue:** Database Webhooks and Scheduling Edge Functions
show a caution that any role that can log in to the database can read
the secret key while the request waits, because `pg_net` writes the
decrypted key to `net.http_request_queue` until it sends the request.
Both pages include the caution from a new partial,
`_partials/pg_net_queued_key.mdx`, which links to the troubleshooting
article.

## Verification (`/test-the-docs`)

| Snippet / step | Class | Sandbox | Result | Notes |
| --- | --- | --- | --- | --- |
| Function with `auth: 'secret'` and `verify_jwt = false`, called with
the secret key on `apikey` | runnable-with-setup | Compose runner +
`supabase start` | pass | Returns `200` |
| Same function, called with the publishable key, then no key |
runnable-with-setup | Compose runner + `supabase start` | pass |
Rejected with `INVALID_API_KEY`, then `MISSING_CREDENTIALS` |
| Scheduling Edge Functions: Vault secrets, then `cron.schedule` |
runnable-with-setup | Compose runner + `supabase start` | pass | The
cron run succeeded, and the function returned `200` |
| Database Webhooks: Vault-based trigger function and trigger |
runnable-with-setup | Compose runner + `supabase start` | pass | Inserts
as `postgres` and as `authenticated` both reached the function with the
documented payload |
| Same trigger function as `security invoker`, insert as `authenticated`
| runnable-with-setup | Compose runner + `supabase start` | pass | Fails
with `permission denied for schema vault`, which confirms the page's
explanation |
| Database Webhooks: Dashboard steps | deferred | — | deferred | Needs a
hosted project. Labels confirmed in the Studio source. |

**Tier A path:** Vault secrets → `pg_cron` job → `pg_net` request → Edge
Function with `auth: 'secret'` returns `200`.

**Environment:** OrbStack; compose sandbox (`sandbox/run.sh`); fences
in-container only. Two deviations from the stock sandbox, neither
committed:

- The sandbox pins Supabase CLI v2.39.2, which predates `sb_secret_` and
`sb_publishable_` keys. The run used v2.121.0.
- The `dind` service doesn't mount `/work`, so local functions fail with
`failed to determine entrypoint`. The function file was copied into the
DinD filesystem.

`project_url` pointed at the local API gateway, because the database
container can't reach the runner's `127.0.0.1`.

## Preview links

- [Database
Webhooks](https://docs-git-alexisa-docs-1458-supabase.vercel.app/docs/guides/database/webhooks)
- [Scheduling Edge
Functions](https://docs-git-alexisa-docs-1458-supabase.vercel.app/docs/guides/functions/schedule-functions)
- [Securing Edge
Functions](https://docs-git-alexisa-docs-1458-supabase.vercel.app/docs/guides/functions/auth)


## Post-merge

> [!IMPORTANT]
> After this PR merges, post the following comment on
[FDBKIN-19992](https://linear.app/supabase/issue/FDBKIN-19992) and close
it:
>
> The "Missing required headers" error happens when the webhook doesn't
send an auth header to the Edge Function. Database Webhooks send only
the headers you configure. [Database Webhooks → Call an Edge
Function](https://supabase.com/docs/guides/database/webhooks#call-an-edge-function)
shows how to add the secret key, either in the Dashboard with **Add
secret key** or in a migration that reads the key from Vault. [Securing
Edge
Functions](https://supabase.com/docs/guides/functions/auth#service-to-service-calls)
covers the function side: disable `verify_jwt` and use `auth: 'secret'`.
2026-10-22 19:19:42 +00:00
..