Send a Discovery Email Magic Link

Stytch's Discovery and Email Magic Link (EML) products allow your end users to discover all of their Organizations upon authentication. Instead of logging in to each Organization separately, the end user can use the Discovery flow to log in once, see all of their memberships, and select an Organization to authenticate into.

In this guide, you'll learn how to use EML with the Discovery flow. By the end, you'll have:

  • Sent a Discovery EML to an end user's email inbox.
  • Returned a list of Discovered Organizations.
  • Authenticated the end user into the desired Organization.

Before you start

In order to complete this guide, you'll need the following:

  • A Stytch B2B project. If you don't have one already, in the Dashboard, click on your existing project name in the top left corner of the Dashboard, click Create a new project, and then select B2B Authentication.
  • The project Test environment's project_id and secret from the API keys section. You'll need to pass these values into the Authorization request header for every Stytch API call.
  • An end user who is a Member of multiple Organizations.

Step 1: Add or update redirect URLs in the Dashboard

As a security precaution, we require that you explicitly set URLs in the Redirect URLs section of the Dashboard. End users are always routed to the redirect URLs after clicking an EML.

By default, all new projects have redirect URLs set to http://localhost:3000/authenticate for the Test environment in the Dashboard.

If your project doesn't have a redirect URL or if you need a different URL, click + Create redirect URL and provide a URL for ALL or Discovery

For more information on why this step is necessary, please check out the resource on URL validation.

Step 2: Call the Send Discovery Email endpoint

To call the [Send Discovery Email endpoint]/(docs/b2b/api/send-discovery-email), provide the email_address of the end user in the request's body parameters.

curl --request POST \
	--url https://test.stytch.com/v1/b2b/magic_links/email/discovery/send \
	-u '{PROJECT_ID}:{SECRET}' \
	-H 'Content-Type: application/json' \
	-d '{
		"email_address": "{EMAIL_ADDRESS}"
	}'

After a successful API call, an email with the Discovery Magic Link will be sent to the email_address, prompting the end user to log in.

Step 3: Authenticate the Discovery Magic Link

When the end user clicks the signup EML, they will be redirected to the redirect URL you entered into the Dashboard in Step 1. Your application should have a route and page that handles the redirect URL and its query parameters. The full Discovery redirect URL will look like this:

{REDIRECT_URL}?stytch_token_type=discovery&token=IAhgyeXM4VLn7Xyq3nHoxyO4w60vtEwSYCJTwPHtr6rg

Call the Authenticate Discovery Magic Link endpoint with the query param token's value.

curl --request POST \
	--url https://test.stytch.com/v1/b2b/magic_links/discovery/authenticate \
	-u 'PROJECT_ID:SECRET' \
	-H 'Content-Type: application/json' \
	-d '{
		"discovery_magic_links_token": "IAhgyeXM4VLn7Xyq3nHoxyO4w60vtEwSYCJTwPHtr6rg"
	}'

After a successful API call:

  • discovered_organizations, an array of Organizations and membership data, will be returned in the response.
  • An intermediate_session_token will be returned in the response.

The response will look like this:

{
	"discovered_organizations": [
		{
			"member_authenticated": true,
			"membership": {
				"details": null,
				"member": {
					"email_address": "example-user@exampleorg.com",
					"is_breakglass": false,
					"member_id": "member-test-3ef4e63c-9bfc-476b-bf4b-58189e445ae3",
					"member_password_id": "",
					"mfa_enrolled": false,
					"mfa_phone_number": "",
					"name": "",
					"oauth_registrations": [],
					"organization_id": "organization-test-e87f5fcf-24e7-41c0-9611-c84faf6c4fe0",
					"sso_registrations": [],
					"status": "active",
					"trusted_metadata": {},
					"untrusted_metadata": {}
				},
				"type": "active_member"
			},
			"organization": {
				"allowed_auth_methods": [],
				"auth_methods": "ALL_ALLOWED",
				"email_allowed_domains": [
					"exampleorg.com"
				],
				"email_invites": "NOT_ALLOWED",
				"email_jit_provisioning": "RESTRICTED",
				"mfa_policy": "OPTIONAL",
				"organization_id": "organization-test-e87f5fcf-24e7-41c0-9611-c84faf6c4fe0",
				"organization_logo_url": "",
				"organization_name": "Org A",
				"organization_slug": "org-a",
				"sso_active_connections": [],
				"sso_default_connection_id": null,
				"sso_jit_provisioning": "NOT_ALLOWED",
				"sso_jit_provisioning_allowed_connections": [],
				"trusted_metadata": {}
			}
		},
		{
			"member_authenticated": true,
			"membership": {
				"details": null,
				"member": {
					"email_address": "example-user@exampleorg.com",
					"is_breakglass": false,
					"member_id": "member-test-539f0cc9-1b39-4f2c-8481-6e190e20fc0a",
					"member_password_id": "",
					"mfa_enrolled": false,
					"mfa_phone_number": "",
					"name": "",
					"oauth_registrations": [],
					"organization_id": "organization-test-591034d2-a59a-4b01-814b-0a35709c02dc",
					"sso_registrations": [],
					"status": "invited",
					"trusted_metadata": {},
					"untrusted_metadata": {}
				},
				"type": "invited_member"
			},
			"organization": {
				"allowed_auth_methods": [],
				"auth_methods": "ALL_ALLOWED",
				"email_allowed_domains": [
					"exampleorg.com"
				],
				"email_invites": "RESTRICTED",
				"email_jit_provisioning": "RESTRICTED",
				"mfa_policy": "OPTIONAL",
				"organization_id": "organization-test-591034d2-a59a-4b01-814b-0a35709c02dc",
				"organization_logo_url": "",
				"organization_name": "Org B",
				"organization_slug": "org-b",
				"sso_active_connections": [],
				"sso_default_connection_id": null,
				"sso_jit_provisioning": "NOT_ALLOWED",
				"sso_jit_provisioning_allowed_connections": [],
				"trusted_metadata": {}
			}
		},
		{
			"member_authenticated": true,
			"membership": {
				"details": null,
				"member": {
					"email_address": "example-user@exampleorg.com",
					"is_breakglass": false,
					"member_id": "member-test-71b6e5c7-0c09-4831-be5a-b0a85d9c3448",
					"member_password_id": "",
					"mfa_enrolled": false,
					"mfa_phone_number": "",
					"name": "",
					"oauth_registrations": [],
					"organization_id": "organization-test-c773858f-5686-4574-a7b7-1768fc6a3388",
					"sso_registrations": [],
					"status": "active",
					"trusted_metadata": {},
					"untrusted_metadata": {}
				},
				"type": "active_member"
			},
			"organization": {
				"allowed_auth_methods": [],
				"auth_methods": "ALL_ALLOWED",
				"email_allowed_domains": [
					"exampleorg.com"
				],
				"email_invites": "ALL_ALLOWED",
				"email_jit_provisioning": "RESTRICTED",
				"mfa_policy": "OPTIONAL",
				"organization_id": "organization-test-c773858f-5686-4574-a7b7-1768fc6a3388",
				"organization_logo_url": "",
				"organization_name": "Org C",
				"organization_slug": "org-c",
				"sso_active_connections": [],
				"sso_default_connection_id": null,
				"sso_jit_provisioning": "ALL_ALLOWED",
				"sso_jit_provisioning_allowed_connections": [],
				"trusted_metadata": {}
			},
		},
		{
			"member_authenticated": true,
			"membership": {
				"details": {
					"domain": "exampleorg.com"
				},
				"member": null,
				"type": "eligible_to_join_by_email_domain"
			},
			"organization": {
				"allowed_auth_methods": [],
				"auth_methods": "ALL_ALLOWED",
				"email_allowed_domains": [
					"exampleorg.com"
				],
				"email_invites": "ALL_ALLOWED",
				"email_jit_provisioning": "RESTRICTED",
				"mfa_policy": "OPTIONAL",
				"organization_id": "organization-test-3a8eb793-089f-46e7-92b6-7074a7c2b515",
				"organization_logo_url": "",
				"organization_name": "Org D",
				"organization_slug": "org-d",
				"sso_active_connections": [],
				"sso_default_connection_id": null,
				"sso_jit_provisioning": "ALL_ALLOWED",
				"sso_jit_provisioning_allowed_connections": [],
				"trusted_metadata": {}
			}
		},
	],
	"email_address": "example-user@exampleorg.com",
	"intermediate_session_token": "D4c1cFGX7jS9QH6o0reHjbCKPParZnGptcQsyRuAtJCW",
	"request_id": "request-id-test-e8d65fc3-08ca-4911-9e1e-2d5291833718",
	"status_code": 200
}

In the example response, the end user is an active Member in two different Organizations, Org A and Org C, an invited Member in another Organization, Org B, and eligible to join Org D.

From here, you have two options for completing the Discovery flow.

Step 4a: Call the Exchange Intermediate Session Token endpoint

To complete the Discovery flow and authenticate into an Organization, call the Exchange Intermediate Session Token endpoint and provide the following:

  • The intermediate_session_token from the response in Step 3.
  • The desired organization_id from the discovered_organizations list in Step 3.
  • The desired session_duration_minutes length.
curl --request POST \
	--url https://test.stytch.com/v1/b2b/discovery/intermediate_sessions/exchange \
	-u '{PROJECT_ID}:{SECRET}' \
	-H 'Content-Type: application/json' \
	-d '{
		"intermediate_session_token": "D4c1cFGX7jS9QH6o0reHjbCKPParZnGptcQsyRuAtJCW=",
		"organization_id": "organization-test-c773858f-5686-4574-a7b7-1768fc6a3388",
		"session_duration_minutes": 60
	}'

After a successful API call:

  • The end user will be authenticated as a Member of the Organization.
  • A Member Session will be returned in the response.
  • The Member will be updated to active if their status was pending or invited.

Step 4b: Call the Create an Organization via Discovery endpoint

Alternatively, to complete the Discovery flow and create a new Organization, call the Create an Organization via Discovery endpoint and provide the following:

  • The intermediate_session_token from the response in Step 3.
  • The organization_name, organization_slug, and any other Organization attributes.
  • The desired session_duration_minutes length. If left empty, the default length for session_duration_minutes will be set to 60.
curl --request POST \
	--url https://test.stytch.com/v1/b2b/discovery/organizations \
	-u '{PROJECT_ID}:{SECRET}' \
	-H 'Content-Type: application/json' \
	-d '{
		"intermediate_session_token": "D4c1cFGX7jS9QH6o0reHjbCKPParZnGptcQsyRuAtJCW",
		"session_duration_minutes": 60,
		"organization_name": "Example Org Inc.",
		"organization_slug": "example-org"
	}'

After a successful API call:

  • An Organization will be created and returned in the response.
  • A Member will be created and returned in the response.
  • A Member Session will be returned in the response.
  • The end user will be authenticated as a Member of the Organization.

What's next

Build a user interface that allows end users to log in with EML and the Discovery flow. You'll also need to build a page to handle the redirect back to your application in Step 3 after an end user clicks on an EML.

Clone our B2B Next.js example app for helpful templates that can get you started quickly. Also, check out our interactive B2B demo app to see the app in action.