Skip to content
TryWebhook
New URL

Backend · Free · No signup

Test and debug Supabase webhooks and auth hooks

Point a Supabase Auth Hook at a throwaway URL and read the delivery as it arrived — the user and email_data blocks, and the three Standard Webhooks headers whose values are all folded into the signature.

No signup · No email · Ready in about a second

At a glance

Supabase webhooks in brief

Category
Backend
Where to configure
Supabase Dashboard → Authentication → Hooks → enable an HTTP hook
Signature header
webhook-signature
Secret
Hook secret (looks like `v1,whsec_…`)
Retries
None. An auth hook runs inside the user's own request, so a failure surfaces to them as a sign-in or sign-up error rather than being queued for later.
Response deadline
5 seconds. The user is waiting on the other end of it.
Treated as success
Any 2xx. A non-2xx from a Send Email or Send SMS hook fails the auth operation that triggered it.

Cross-checked against Supabase's own webhook documentation. Providers change these; if something here is stale, tell us.

The payload

What Supabase actually sends

Shape-accurate, with obviously fake identifiers. These are the same samples the inspector's Send test webhook panel fires, so you can read one here and then send the identical bytes to your own URL.

POST/w/{your-bin-id}551 B

Signed with Standard Webhooks, like Svix. Blocks signup if it fails.

3 headers
webhook-id
msg_2fakeSupabaseHookId
webhook-timestamp
1750000000
user-agent
Supabase/2
Body · application/json · 1 line
{"user":{"id":"00000000-1111-2222-3333-444444444444","aud":"authenticated","role":"","email":"buyer@example.com","phone":"","app_metadata":{"provider":"email","providers":["email"]},"user_metadata":{"full_name":"Ada Lovelace"},"created_at":"2026-06-15T10:04:00Z","updated_at":"2026-06-15T10:04:00Z"},"email_data":{"token":"000000","token_hash":"fake0000000000000000000000000000000000000000000000000000000000","redirect_to":"https://example.com/welcome","email_action_type":"signup","site_url":"https://example.com","token_new":"","token_hash_new":""}}

No sample carries a signature header. A valid one can only be produced with your own signing secret, and a fabricated one would make the Signature tab report a mismatch that isn't real. Send a genuine delivery when you want to test verification.

Verification

Verifying a Supabase signature

Signed payload

A space-separated list of versioned signatures, each `v1,<base64>`. The signed string is `{webhook-id}.{webhook-timestamp}.{raw body}`, and the HMAC key is the base64-decoded portion of the hook secret after the `v1,whsec_` prefix.

Implemented as: HMAC-SHA256 over `{id}.{timestamp}.{raw body}`, base64, key = base64-decoded secret.

Built in. Open any captured request, switch to the Signature tab, pick Svix / Standard Webhooks, and paste your hook secret (looks like `v1,whsec_…`). The HMAC is computed in your browser with the Web Crypto API over the raw bytes that arrived — the secret is never sent to us, and never written to disk.

Events

Events worth subscribing to first

Not the full catalogue — the handful that carry most integrations. Subscribing to everything is the fastest way to a handler that times out.

EventFires when
Send EmailSupabase is about to send a sign-up confirmation, magic link, recovery, or invite email. Your endpoint sends it instead, using the token and redirect from `email_data`.
Send SMSA phone OTP is being issued. Same idea as Send Email, for your own SMS provider.
Custom Access TokenA JWT is being minted. You return modified claims, so this hook must answer with a body — a capture bin cannot, and sign-in fails while it is pointed here.
MFA Verification AttemptA second-factor code was submitted. You return a decision, which makes this another hook that needs a real response.
Password Verification AttemptA password was checked. Used for custom lockout and alerting policies, and it also expects a response body.

Enabling a hook against a test URL

Create a URL above, then open Supabase Dashboard → Authentication → Hooks. Pick Send Email, choose HTTPS as the hook type, paste the URL, and save. Supabase generates a hook secret shaped like v1,whsec_… — copy it now.

Do this in a project you do not care about. While a Send Email hook is enabled, Supabase stops sending its own emails and delegates to your endpoint; a capture bin receives the payload and sends nothing, so confirmation links never arrive. That is exactly what you want for inspection and exactly what you do not want in production.

Then trigger it: sign up a user, or call supabase.auth.signInWithOtp({ email }). The delivery appears here within a second.

Locally, the equivalent lives in supabase/config.toml:

[auth.hook.send_email]
enabled = true
uri = "https://trywebhook.com/w/your-bin-id"
secrets = "env(SEND_EMAIL_HOOK_SECRET)"

Reading a Send Email payload

{
  "user": { "id": "00000000-0000-4000-8000-000000000000", "email": "buyer@example.com" },
  "email_data": {
    "token": "000000",
    "token_hash": "...",
    "redirect_to": "https://example.com/welcome",
    "email_action_type": "signup",
    "site_url": "https://example.com",
    "token_new": "",
    "token_hash_new": ""
  }
}

email_action_type is what you switch on — signup, magiclink, recovery, invite, email_change. It decides which template you render, and it is the only thing distinguishing an account confirmation from a password reset.

token is the six-digit OTP; token_hash is what goes into a confirmation link, as /auth/v1/verify?token={token_hash}&type={email_action_type}&redirect_to={redirect_to}. Include one, the other, or both, depending on whether your users type a code or click a link.

token_new and token_hash_new are empty except during an email change, where the old and new addresses each get their own token. An implementation that ignores them silently breaks email changes when secure email change is on.

Verifying the signature

Three headers, all part of what is signed:

webhook-id: msg_2fakeSupabaseHookId
webhook-timestamp: 1750000000
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=

Build the signed content by joining the id, the timestamp, and the raw body with literal dots. Derive the key by stripping the prefix and base64-decoding:

const key = base64Decode(hookSecret.replace(/^v1,whsec_/, ''));
const signed = `${id}.${timestamp}.${rawBody}`;

HMAC-SHA256, base64-encode, compare against each v1, entry in the header. Then reject timestamps more than five minutes old.

Note the prefix. Clerk’s secrets start with whsec_; Supabase’s start with v1,whsec_. It is the same specification and the same code path — but a regular expression that only strips whsec_ leaves a stray v1, in the string, and every signature fails. In the Signature tab above, choose Svix / Standard Webhooks and paste the secret exactly as the dashboard shows it.

Database Webhooks, briefly

If what you are debugging fires on an INSERT rather than on a sign-up, you are looking at the other feature. Database Webhooks are triggers that call pg_net:

{
  "type": "INSERT",
  "table": "orders",
  "schema": "public",
  "record": { "id": 1, "total": 2900 },
  "old_record": null
}

They are unsigned. There is no HMAC, no timestamp, and no retry — pg_net sends the request and forgets it, and a failure is visible only in the net._http_response table. Whatever authentication you want has to be a header you configure and check by hand, so use a long random bearer token and compare it in constant time.

They also fire inside the database transaction’s aftermath rather than in a user’s request, which means they are safe to point at a bin. Capture one here to see the record/old_record shape — it is the fastest way to learn which columns actually arrive.

Failure modes

Where Supabase integrations usually break

Auth hooks are not Database Webhooks

Two unrelated features. Auth Hooks are Standard Webhooks, signed, in-line with the auth flow. Database Webhooks are `pg_net` calls fired by a table trigger — unsigned, fire-and-forget, with only whatever custom header you configure. Advice about one rarely applies to the other.

The secret has two prefixes to strip

`v1,whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`. Drop `v1,whsec_`, then base64-decode what is left to get the HMAC key. Clerk's secret has only the `whsec_` half, so a verifier ported from Clerk trips on the `v1,` and fails with no useful message.

Some hooks must reply, not just receive

Custom Access Token, MFA, and Password Verification hooks are request-response — Supabase uses your body to decide what to do. Pointing one at a capture bin breaks sign-in for real users while it is enabled. Only Send Email and Send SMS are safe to inspect this way, and only in a project nobody depends on.

All three headers are signed

`{webhook-id}.{webhook-timestamp}.{body}`, joined by literal dots. Verifying the body alone never matches, and a proxy that strips unfamiliar headers breaks verification while leaving the payload untouched.

Local and hosted are configured separately

Locally the hook is declared in `supabase/config.toml` with its own secret, usually from an environment variable. In a hosted project it is set in the dashboard. Neither reads the other, so a hook that works with `supabase start` may simply not exist in production.

Answers

Supabase webhook questions

What is the difference between a Supabase Auth Hook and a Database Webhook?+

An Auth Hook fires during authentication and is signed with the Standard Webhooks scheme. A Database Webhook is a Postgres trigger that calls your URL through `pg_net` when a row changes — it is unsigned, is not retried, and carries a `record`/`old_record` payload instead.

Can I verify a Supabase hook signature here?+

Yes. Choose Svix / Standard Webhooks in the Signature tab and paste the hook secret including the `v1,whsec_` prefix. The prefix stripping, the base64 decode, and the `{id}.{timestamp}.{body}` construction are all handled.

Is it safe to point an auth hook at a temporary URL?+

For Send Email and Send SMS in a development project, yes — the emails simply do not get sent. For Custom Access Token, MFA, or Password Verification, no: Supabase needs your response to continue, so sign-in will fail for anyone using that project.

Why does my signature check fail with the correct secret?+

Two likely causes. Either the `v1,` prefix was not stripped before the base64 decode, or the secret was used as an ASCII string rather than being decoded to bytes at all. Both produce a valid-looking digest that never matches.

Are Database Webhooks signed?+

Not by Supabase. You add your own header — a bearer token or a shared secret — in the webhook's configuration, and check it yourself. There is no HMAC over the body, so treat that header as the entire authentication story and keep it out of logs.

Other providers

Testing something else?

One click

Point Supabase at a URL and watch the payload land.

Supabase Dashboard → Authentication → Hooks → enable an HTTP hook — paste the URL, trigger an event, and read exactly what arrived.

No signup · No email · Ready in about a second