Skip to content
IRONDOME
Docs menu

Platform guides

Block disposable emails in Supabase Auth

Use IronDome with Supabase’s Before User Created hook to reject disposable-email signups. Follow setup, test outcomes and troubleshoot failures.

On this page

Use IronDome as a Supabase Auth Before User Created HTTP hook. Supabase sends a signed signup event to IronDome before creating the user. IronDome classifies the email and allows or rejects that signup.

This integration requires no customer-hosted Edge Function, Supabase service-role key, or API key in the hook payload.

Before you start

You need an IronDome account with Supabase integration access, your Supabase project reference, and permission to configure that project's Auth hooks. Test in a development project before enabling production signup protection.

The hook applies to creation of new users. It does not rescreen existing users, sign-ins, or email changes. Phone-only and anonymous users without an email proceed without an email classification.

Create an installation

Installation management uses your signed-in account session at app.irondome.dev, not an email-check API key. Each installation belongs to your account and one Supabase project.

Setup currently uses the management API rather than a dashboard setup wizard. Sign in at https://app.irondome.dev, open that page's browser developer tools, and use its Console in the top-level app context. Run the helper below there, not in your Supabase project, application frontend, or terminal. Mutations require X-IDM-Request: 1; the browser supplies the matching Origin and session cookie. A stale session may require signing in again.

JavaScript
async function manageInstallation(path, input) {
  const response = await fetch(path, {
    method: input === undefined ? 'GET' : 'POST',
    credentials: 'same-origin',
    headers: input === undefined ? {} : {
      'Content-Type': 'application/json',
      'X-IDM-Request': '1',
    },
    ...(input === undefined ? {} : { body: JSON.stringify(input) }),
  });
  const result = await response.json();
  if (!response.ok) {
    throw new Error(result.error?.code ?? 'installation_request_failed');
  }
  return result;
}

const installation = await manageInstallation('/api/integrations/supabase', {
  project_ref: 'YOUR_PROJECT_REF',
});

Replace YOUR_PROJECT_REF with your project's 20-character lowercase reference. The response contains id, url, secret, and publication_status. Save the ID and signing secret securely. Do not log the response or send its secret to your application's frontend.

The returned hook URL has this form:

Code
https://api.irondome.dev/v1/hooks/supabase/INSTALLATION_ID/before-user-created

Use the returned URL exactly. Do not put your API key or signing secret in the URL. The signing secret is a full value beginning with v1,whsec_; it is separate from an API key.

Wait for publication

Creation is asynchronous. Read your installations with:

JavaScript
const { installations } =
  await manageInstallation('/api/integrations/supabase');

Find the returned ID and inspect publication.publication_status. Wait for published before enabling the hook. A pending response means publication is not complete; creating more installations does not fix it.

If publication remains pending or health becomes critical, keep the hook disabled and contact support. Publication acknowledgement does not establish that every edge location has observed a change.

Configure Supabase Auth

  1. In the Supabase Dashboard, open Authentication → Hooks and configure Before User Created as an HTTP hook.
  2. Paste the installation's returned hook URL.
  3. Set the signing secret to the complete returned secret value, including v1,whsec_.
  4. Enable the hook after IronDome publication completes.

If your project already has a Before User Created policy, review how to compose it rather than replacing it without preserving its behavior. See Supabase's hook guide for project configuration.

Signup outcomes

Outcome Hook response Signup result
Non-disposable email HTTP 204 Proceed with normal account creation
Disposable email HTTP 200, error.http_code: 403 Reject and ask for a permanent address
Invalid email HTTP 200, error.http_code: 400 Reject and ask for a corrected address
Free quota exhausted HTTP 200, error.http_code: 429 Reject until quota or authorized plan changes
Authorization or service unavailable HTTP 200, error.http_code: 503 Reject; user can try later
Invalid signature or malformed event Transport HTTP 401 or 400 Reject the delivery

Supabase interprets the error envelope: transport HTTP 200 does not always mean signup is allowed. Do not substitute an unchecked allow response when protection is unavailable.

Successful classifications, including disposable outcomes, use your account's plan. Free hook usage shares the organization's monthly allowance with API checks. Do not add another API check for the same signup when this hook already enforces it. See plans and limits.

Test the hook before production

Use an authorized development project. Confirm that its IronDome installation is published, its hook is configured, and your application's Supabase client points to that same project. Choose two fresh, unregistered addresses you control: one permanent and one known disposable address. Reusing an existing user can make the result ambiguous; a made-up example.com address is not a detector test fixture.

Run one signup at a time through your development application's normal signup flow. For an email/password flow, use its existing client with Supabase's signUp method, not the IronDome management console:

JavaScript
// Development app only. Supply TEST_EMAIL and TEST_PASSWORD locally.
const { data, error } = await supabase.auth.signUp({
  email: TEST_EMAIL,
  password: TEST_PASSWORD,
});

console.log({
  userReturned: Boolean(data.user),
  errorStatus: error?.status ?? null,
  errorCode: error?.code ?? null,
});
// Do not log sessions, tokens, passwords, or raw user records.

Check the client result and Authentication → Users in the same development project:

  • Permanent address: signup proceeds and the user exists. A missing session can be normal when email confirmation is required.
  • Disposable address: the client reports the disposable-email rejection and no user is created. An unrelated signup error is not proof that the hook blocked the address; exact client error codes can vary by Supabase version.

The disposable outcome has this hook callback body, sent with transport HTTP 200:

JSON
{
  "error": {
    "http_code": 403,
    "message": "Disposable email addresses are not allowed. Please use a permanent email address."
  }
}

This is not the browser's signup response: Supabase interprets the hook's error envelope before returning to your client. Record the test time, client outcome, and presence or absence of user records without sharing addresses or secrets. Keep mailbox ownership verification and other signup abuse controls in place.

Rotate the signing secret

Keep the installation ID and use the same management helper:

JavaScript
const rotated = await manageInstallation(
  `/api/integrations/supabase/${installation.id}/rotate`,
  {},
);
  1. Save the returned new secret. IronDome schedules a registration accepting both generations.
  2. Wait for that registration's publication status to become published.
  3. Replace the secret in Supabase with the new full value.
  4. Perform a controlled signup and confirm it succeeds. Allow time for the signed delivery to appear in accounting.
  5. Retire the previous generation:
JavaScript
await manageInstallation(
  `/api/integrations/supabase/${installation.id}/retire`,
  {},
);

Retirement requires published rotation state and an observed delivery signed by the new secret. A 409 new_secret_delivery_not_observed means proof has not arrived; confirm the Supabase secret and delivery before trying again. Wait for publication of the new-only registration after retirement.

Do not promise immediate propagation or zero-downtime rotation. Keep both secrets protected until retirement is confirmed.

Disconnect

  1. Disable or remove the Before User Created hook in Supabase first. Decide whether signup should continue without this protection or be paused.
  2. Disable the IronDome installation:
JavaScript
await manageInstallation(
  `/api/integrations/supabase/${installation.id}/disable`,
  {},
);
  1. Inspect the installation until disable publication completes. Retain its ID and status for troubleshooting, then remove unused local secret copies.

Disabling only in IronDome while leaving the Supabase hook active can block signup. A pending disable is not proof of global revocation, and publication has no guaranteed global cutoff time. For urgent containment, disable signup or disconnect the hook directly in Supabase.

Troubleshoot setup

Supabase requires HTTP Auth hooks to finish within five seconds. A timeout blocks signup; it does not mean the email was allowed. Check service status and the installation's publication state before retrying. This provider limit is not a promised IronDome response time. See Supabase Auth Hooks.

Problem Check
invalid_project Use the project reference, not its URL, display name, or API key.
integration_unavailable Integration access or configuration is unavailable; contact support.
integration_capacity_or_account_unavailable Account access or installation capacity needs attention.
rotation_already_pending Finish the current rotation before starting another.
rotation_publication_pending Wait for the two-secret registration to publish.
new_secret_delivery_not_observed Switch Supabase to the new secret and verify a signed signup delivery.
Signup fails after disconnect Remove the configured Supabase hook; disabling IronDome alone is insufficient.

A non-disposable result still does not establish mailbox ownership or deliverability. Keep your normal verification and signup-abuse controls.