/
Contact usSee pricingStart building
Node
​

    About Stytch

    Introduction
    Integration Approaches
      Full-stack overview
      Frontend (pre-built UI)
      Frontend (headless)
      Backend
    Migrations
      Migration overview
      Migrating users statically
      Migrating users dynamically
      Additional migration considerations
      Zero-downtime deployment
      Defining external IDs for users
      Migrating from Stytch Consumer to B2B
      Exporting from Stytch
    Custom Domains
      Overview

    Authentication

    DFP Protected Auth
      Overview
      Setting up DFP Protected Auth
      Handling challenges
    Magic Links
    • Email Magic Links

      • Getting started with the API
        Getting started with the SDK
        Replacing your password reset flow
        Building an invite user flow
        Add magic links to an existing auth flow
        Adding PKCE to a Magic Link flow
        Magic Link redirect routing
    • Embeddable Magic Links

      • Getting started with the API
    MFA
      Overview
      Backend integration
      Frontend integration
      Remembered device flow
    Mobile Biometrics
      Overview
    M2M Authentication
      Authenticate an M2M Client
      Rotate client secrets
      Import M2M Clients from Auth0
    OAuth
    • Identity providers

      • Overview
        Provider setup
      Getting started with the API (Google)
      Add Google One Tap via the SDK
      Email address behavior
      Adding PKCE to an OAuth flow
    Connected Apps
      Overview
      Getting started with the SDK
      Getting started with the API
      Client types
      OAuth scopes
    • Integration Guides

      • Integrate with an Existing Auth System
        MCP Authorization Overview
        Integrate with MCP servers deployed on Cloudflare
        Integrate with MCP servers on Vercel
        Integrate with CLI Apps
        Integrate with AI agents
    • Resources

      • Consent Management
        Custom Domains
    Passcodes
      Getting started with the API
      Getting started with the SDK
    • Toll fraud

      • What is SMS toll fraud?
        How you can prevent toll fraud
      Unsupported countries
    Passkeys & WebAuthn
    • Passkeys

      • Passkeys overview
        Set up Passkeys with the frontend SDK
    • WebAuthn

      • Getting started with the API
        Getting started with the SDK
    Passwords
      Getting started with the API
      Getting started with the SDK
      Password strength policy
    • Email verification

      • Overview
        Email verification before password creation
        Email verification after password creation
    Sessions
      How to use sessions
      Backend integrations
      Frontend integrations
      Custom claims
      Custom claim templates
      Session tokens vs JWTs
      How to use Stytch JWTs
    TOTP
      Getting started with the API
      Getting started with the SDK
    Web3
      Getting started with the API
      Getting started with the SDK
    Trusted Auth Tokens
      Overview
      Getting Started with External IDPs
      Getting Started with Custom Auth Factors
    Device History
      New device notifications

    RBAC

    Resources
      Overview
      Role assignment
    Integration Guides
      Start here
      Backend integration
      Headless frontend integration
      (Legacy) Implement RBAC with metadata

    3rd Party Integrations

    Planetscale
    Supabase
    Feathery
    Unit

    Testing

    E2E testing
    Sandbox values
Get support on SlackVisit our developer forum

Contact us

Consumer Authentication

/

Guides

/

Authentication

/

Connected Apps

/

Resources

/

Custom Domains

Custom Domains and Connected Apps

Why domains matter in OAuth

In OAuth and OpenID Connect (OIDC), many core features rely on a single authoritative domain.

The most important of these is the issuer — a required field in Access and ID token JWTs that uniquely identifies your Stytch application as the entity that created the token. Consumers of these JWTs use the issuer during validation to ensure the token’s authenticity.

Another key feature is the set of well-known metadata URLs, which OAuth clients use to automatically discover server configuration. These endpoints expose metadata about available capabilities and supported flows.

  • For OAuth: https://<domain>/.well-known/oauth-authorization-server
  • For OIDC: https://<domain>/.well-known/openid-configuration

Stytch will automatically provision a default domain for your project upon creation. Default domains are intended for convenient testing of an initial integration. However, this domain is frequently shared with external consumers of your application. We recommend setting up a Branded Custom Domain as a subdomain of your main website before going live with your integration.

Configuring Stytch SDKs to use your domain

Stytch SDKs must be configured to use the correct domain for your project:

const client = new stytch.Client({
  project_id: "PROJECT_ID",
  secret: "SECRET",
  custom_base_url: "${projectDomain}",
});
client = Client(
  project_id="PROJECT_ID",
  secret="SECRET",
  custom_base_url="${projectDomain}",
)
client = Stytch::Client.new(
  project_id: "PROJECT_ID",
  secret: "SECRET"
  custom_base_url: "${projectDomain}",
)
stytchClient, err := stytchapi.NewClient(
    "PROJECT_ID",
    "SECRET",
    stytchapi.WithBaseURI("${projectDomain}")
)
const client = createStytchUIClient(
  "PUBLIC_TOKEN",
  {
    endpointOptions: {
      testApiDomain: "${projectDomain}", // In Test
      apiDomain: "${projectDomain}", // In Live
    }
  }
);

How Custom Domains affect Connected Apps

Stytch will dynamically determine JWT issuers and well-known URL responses based on the domain used to access the Stytch service.

Using the default project domain

All Stytch projects are provisioned a unique default domain upon creation.

  • In test environments, the domain will look like https://{noun}-{verb}-{number}.customers.stytch.dev.
  • In live environments, the domain will look like https://{noun}-{verb}-{number}.customers.stytch.com.

API calls occurring over this domain will use the domain value while processing the request.

For example, the Access token and ID token returned from this Exchange Authorization Code call will have an issuer of https://{noun}-{verb}-{number}.customers.stytch.dev.

curl -X POST https://{noun}-{verb}-{number}.customers.stytch.dev/oauth2/token \
	-H 'Content-Type: application/json' \
	-d '{
		"client_id": "connected-app-test-d731954d-dab3-4a2b-bdee-07f3ad1be888",
		"client_secret": "NHQhc7ZqsXJVtgmN2MXr1etqsQrGAwJ-iBWNLKY7DzJq",
		"redirect_uri": "https://example.com/callback",
		"grant_type": "authorization_code",
		"code": "PvC5UudZ7TPZbELt95yXAQ-8MeEUCRob8bUQ-g52fIJs"
	}'
# => { "access_token": "eyJ...", "id_token": "eyJ...", ... }

Similarly, the metadata returned from this Get OpenID Configuration call will use the https://{noun}-{verb}-{number}.customers.stytch.dev domain:

curl --request GET \
	--url https://https://{noun}-{verb}-{number}.customers.stytch.dev/.well-known/openid-configuration

like so:

{
  "registration_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/register",
  "token_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/token",
  "userinfo_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/userinfo",
  ...
}

Using a branded custom domain (CNAME)

When you configure a custom domain (e.g. https://login.yourcompany.com) and make API calls using that domain, Stytch will use that domain instead.

The Access token and ID token returned from this call will have an issuer of https://login.yourcompany.com.

curl -X POST https://login.yourcompany.com/oauth2/token \
	-H 'Content-Type: application/json' \
	-d '{
		"client_id": "connected-app-test-d731954d-dab3-4a2b-bdee-07f3ad1be888",
		"client_secret": "NHQhc7ZqsXJVtgmN2MXr1etqsQrGAwJ-iBWNLKY7DzJq",
		"redirect_uri": "https://example.com/callback",
		"grant_type": "authorization_code",
		"code": "PvC5UudZ7TPZbELt95yXAQ-8MeEUCRob8bUQ-g52fIJs"
	}'
# => { "access_token": "eyJ...", "id_token": "eyJ...", ... }

Similarly, the metadata returned from this call will use the https://login.yourcompany.com domain:

curl --request GET \
	--url https://login.yourcompany.com/.well-known/openid-configuration

like so:

{
  "registration_endpoint": "https://login.yourcompany.com/v1/oauth2/register",
  "token_endpoint": "https://login.yourcompany.com/v1/oauth2/token",
  "userinfo_endpoint": "https://login.yourcompany.com/v1/oauth2/userinfo",
  ...
}

Migrating from api.stytch.com

If you were an early customer of Connected Apps, you may be accessing the product over api.stytch.com - e.g. https://api.stytch.com/v1/public/${projectId}/oauth2/token. When accessed this way, Stytch will use the project ID (stytch.com/${projectId}) as the issuer. These issuers are not OpenID Connect (OIDC) compliant, as they are not HTTPS URLs.

Why does this matter?

  • Many OIDC clients and libraries will reject tokens if the issuer does not match the expected https URL.
  • Using a custom domain ensures maximum compatibility and security for your integrations.

To get a fully OIDC-compliant issuer:

  1. Set up a custom domain (CNAME) in your DNS that points to Stytch and configure Stytch to recognize this domain.
  2. Configure your application to make all API calls to Stytch over this domain.
  3. Use this domain for all authentication flows.

All Stytch backend SDKs will accept both the old and new format issuers when performing JWT validation to ensure a seamless transition.

Why domains matter in OAuth

Configuring Stytch SDKs to use your domain

How Custom Domains affect Connected Apps

Using the default project domain

Using a branded custom domain (CNAME)

Migrating from api.stytch.com