diff --git a/apps/docs/content/guides/auth/auth-hooks.mdx b/apps/docs/content/guides/auth/auth-hooks.mdx
index 0286afb148f..04fef7cbb09 100644
--- a/apps/docs/content/guides/auth/auth-hooks.mdx
+++ b/apps/docs/content/guides/auth/auth-hooks.mdx
@@ -313,23 +313,37 @@ Hooks return status codes based on the nature of the response. These status code
| HTTP Status Code | Description | Example Usage |
| ---------------- | ------------------------------------------------------------- | ---------------------------------------------- |
| 200, 202, 204 | Valid response, proceed | Successful processing of the request |
-| 429, 503 | Retry-able errors with Retry-after header supported | Temporary server overload or maintenance |
| 403, 400 | Treated as Internal Server Errors and return a 500 Error Code | Malformed requests or insufficient permissions |
+| 429, 503 | Retry-able errors | Temporary server overload or maintenance |
-Errors are responses which contain status codes 400 and above. On a retry-able error, such as an error with a `429` or `503` status code, HTTP Hooks will attempt up to three retries with a back-off of two seconds.
+
+
+`204` Status is not supported by the following hooks which require a response body:
+
+- [Custom Access Token](/docs/guides/auth/auth-hooks/custom-access-token-hook)
+- [MFA Verification Attempt](/docs/guides/auth/auth-hooks/mfa-verification-hook)
+- [Password Verification Attempt](/docs/guides/auth/auth-hooks/password-verification-hook)
+
+
+
+Errors are responses which contain status codes 400 and above. On a retry-able error, such as an error with a `429` or `503` status code, HTTP Hooks will attempt up to three retries with a back-off of two seconds. We have a time budget of 5s for the entire webhook invocation, including retry requests.
Here's a sample HTTP retry schedule:
-| Time Since Start (HH:MM:SS) | Event | Notes |
-| --------------------------- | ----------------------- | ------------------------------------------------- |
-| 00:00:00 | Initial Attempt | Initial invocation begins. |
-| 00:00:05 | Initial Attempt Timeout | Initial invocation must complete. |
-| 00:00:07 | Retry Start #1 | After 2 sec delay, first retry begins. |
-| 00:00:12 | Retry Timeout #1 | First retry timeout. |
-| 00:00:14 | Retry Start #2 | After 2 sec delay, second retry begins. |
-| 00:00:19 | Retry Timeout #2 | Second retry timeout. Returns an error on failure |
+| Time Since Start (HH:MM:SS) | Event | Notes |
+| --------------------------- | --------------------- | -------------------------------------------------------------------------------- |
+| 00:00:00 | Initial Attempt | Initial invocation begins. |
+| 00:00:02 | Initial Attempt Fails | Initial invocation returns `429` or `503` with non-empty `retry-after` header. |
+| 00:00:04 | Retry Start #1 | After 2 sec delay, first retry begins. |
+| 00:00:05 | Retry Timeout #1 | First retry times out, exceeded 5 second budget and invocation returns an error. |
-Return a retry-able error by attaching a appropriate status code (`429`, `503` ) and a non-empty `retry-after` header
+Return a retry-able error by attaching a appropriate status code (`429`, `503`) and a non-empty `retry-after` header
+
+
+
+`Retry-After` Supabase Auth does not fully support the `Retry-After` header as described in RFC7231, we only check if it is a non-empty value such as `true` or `10`. Setting this to your preferred value is fine as a future update may address this.
+
+
```jsx
return new Response(
diff --git a/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx b/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx
index d18fd214ab2..7b765a018ca 100644
--- a/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx
+++ b/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx
@@ -17,6 +17,33 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
| Disabled | Enabled | Email Signups Disabled |
| Disabled | Disabled | Email Signups Disabled |
+## Email change behavior and token hash mapping
+
+When `email_action_type` is `email_change`, the hook payload can include one or two OTPs and their hashes. This depends on your [Secure Email Change](/dashboard/project/_/auth/providers?provider=Email) setting.
+
+- Secure Email Change enabled: two OTPs are generated, one for the current email (`user.email`) and one for the new email (`user.email_new`). You must send two emails.
+- Secure Email Change disabled: only one OTP is generated for the new email. You send a single email.
+
+
+
+Important quirk (backward compatibility):
+
+- `email_data.token_hash_new` = Hash(`user.email`, `email_data.token`)
+- `email_data.token_hash` = Hash(`user.email_new`, `email_data.token_new`)
+
+This naming is historical and kept for backward compatibility. Do not assume that the `_new` suffix refers to the new email.
+
+
+
+### What to send
+
+If both `token_hash` and `token_hash_new` are present, send two messages:
+
+- To the current email (`user.email`): use `token` with `token_hash_new`.
+- To the new email (`user.email_new`): use `token_new` with `token_hash`.
+
+If only one token/hash pair is present, send a single email. In non-secure mode, this is typically the new email OTP. Use `token` with `token_hash` or `token_new` with `token_hash`, depending on which fields are present in the payload.
+
**Inputs**
| Field | Type | Description |
@@ -282,7 +309,15 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
},
"email_action_type": {
"type": "string",
- "enum": ["signup", "invite", "magiclink", "recovery", "email_change", "email"]
+ "enum": [
+ "signup",
+ "invite",
+ "magiclink",
+ "recovery",
+ "email_change",
+ "email",
+ "reauthentication"
+ ]
},
"site_url": {
"type": "string",
@@ -578,6 +613,7 @@ import { readAll } from 'https://deno.land/std/io/read_all.ts'
const postmarkEndpoint = 'https://api.postmarkapp.com/email'
// Replace this with your email
const FROM_EMAIL = 'myemail@gmail.com'
+const PROJECT_REF = ''
// Email Subjects
const subjects = {
@@ -642,8 +678,14 @@ const templates = {
}
function generateConfirmationURL(email_data) {
- // TODO: replace the ref with your project ref
- return `https://[.supabase.co/auth/v1/verify?token=${email_data.token_hash}&type=${email_data.email_action_type}&redirect_to=${email_data.redirect_to}`
+ const baseUrl = `https://${PROJECT_REF}.supabase.co/auth/v1/verify`
+ const params = new URLSearchParams({
+ token: email_data.token_hash,
+ type: email_data.email_action_type,
+ redirect_to: email_data.redirect_to,
+ })
+
+ return `${baseUrl}?${params.toString()}`
}
Deno.serve(async (req) => {
]