Run your own Postgres function or HTTPS endpoint at points of the auth flow: custom token claims, your own e-mail or SMS sender, sign-up rules and lockouts.
| Hook | Runs | Typical use |
|---|---|---|
| Customize access token | Before every access token is issued | Add roles, tenant id or plan to the JWT |
| Send e-mail | Instead of sending an auth e-mail | Your own e-mail provider or templates |
| Send SMS | Instead of sending an SMS code | Your own SMS provider |
| MFA verification attempt | On each MFA code attempt | Lock out after too many failures |
| Password verification attempt | On each password sign-in attempt | Your own lockout rules |
| Before user created | Before a new user is stored | Allow only certain e-mail domains, invite-only sign-up |
#Postgres function hooks
Create a function that takes one jsonb argument and returns jsonb, then set the hook to pg-functions://postgres/<schema>/<function> in Studio → Authentication → Hooks. Saving checks that the function exists. Revoke execute from anon, authenticated and public so it can't be called through the API.
create or replace function public.custom_access_token_hook(event jsonb) returns jsonb
language plpgsql stable as $$
declare claims jsonb := event->'claims';
begin
claims := jsonb_set(claims, '{tenant_id}', to_jsonb((select tenant_id from public.profiles where id = (event->>'user_id')::uuid)));
return jsonb_set(event, '{claims}', claims);
end $$;
revoke execute on function public.custom_access_token_hook(jsonb) from anon, authenticated, public;-- Before user created: reject sign-ups outside your company domain
create or replace function public.only_company(event jsonb) returns jsonb language plpgsql as $$
begin
if event->'user'->>'email' not like '%@acme.com' then
return jsonb_build_object('error', jsonb_build_object('http_code', 403, 'message', 'Use your acme.com address'));
end if;
return '{}'::jsonb;
end $$;#HTTPS hooks
Set the hook to an https:// URL on a public host. Each request is signed with the Standard Webhooks scheme (webhook-id, webhook-timestamp, webhook-signature headers); get the secret (v1,whsec_…) with Show signing secret and verify every request. Rotate it with Rotate secret on save. Respond within a few seconds with the JSON the hook expects (for most hooks {}; to reject, {"error": {"http_code": 403, "message": "…"}}).