boundless-ambulance-56979
07/29/2025, 9:21 PMdsn: <cockroach://root@cockroach:26257/kratos_db?sslmode=disable>
serve:
public:
base_url: <http://127.0.0.1:4433/>
cors:
enabled: true
allowed_origins:
- <http://localhost:5173>
- <http://localhost:5174>
- <http://localhost:3000>
- <http://localhost:4455>
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Content-Type
- Cookie
exposed_headers:
- Content-Type
- Set-Cookie
allow_credentials: true
admin:
base_url: <http://127.0.0.1:4434/>
selfservice:
default_browser_return_url: <http://localhost:5173/enrollment>
flows:
login:
ui_url: <http://localhost:5173/login>
registration:
ui_url: <http://localhost:5173/registration>
enabled: true
after:
default_browser_return_url: <http://localhost:5173/enrollment>
password:
hooks:
- hook: show_verification_ui
- hook: show_recovery_ui
settings:
ui_url: <http://127.0.0.1:4455/settings>
recovery:
ui_url: <http://127.0.0.1:4455/recovery>
verification:
ui_url: <http://127.0.0.1:4455/verification>
methods:
password:
enabled: true
identity:
default_schema_id: default
schemas:
- id: default
url: file:///etc/config/kratos/identity-schemas/tpp.schema.json
courier:
templates:
verification:
valid:
email:
body:
html: file:///etc/config/kratos/email-templates/verification/body.html.gotmpl
plaintext: file:///etc/config/kratos/email-templates/verification/body.plaintext.gotmpl
subject: file:///etc/config/kratos/email-templates/verification/subject.gotmpl
delivery_strategy: smtp
smtp:
connection_uri: <smtps://sandbox-3>:***@smtp.devmail.email:2525/
from_address: <mailto:[email protected]|[email protected]>
from_name: FAPI Gatewayboundless-ambulance-56979
07/29/2025, 9:21 PMchilly-king-10285
07/29/2025, 9:21 PMchilly-king-10285
07/29/2025, 9:21 PMboundless-ambulance-56979
07/29/2025, 9:21 PMdsn: <cockroach://root@cockroach:26257/kratos_db?sslmode=disable>
serve:
public:
base_url: <http://127.0.0.1:4433/>
cors:
enabled: true
allowed_origins:
- <http://localhost:5173>
- <http://localhost:5174>
- <http://localhost:3000>
- <http://localhost:4455>
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Content-Type
- Cookie
exposed_headers:
- Content-Type
- Set-Cookie
allow_credentials: true
admin:
base_url: <http://127.0.0.1:4434/>
selfservice:
default_browser_return_url: <http://localhost:5173/enrollment>
flows:
login:
ui_url: <http://localhost:5173/login>
registration:
ui_url: <http://localhost:5173/registration>
enabled: true
after:
default_browser_return_url: <http://localhost:5173/enrollment>
password:
hooks:
- hook: show_verification_ui
- hook: show_recovery_ui
settings:
ui_url: <http://127.0.0.1:4455/settings>
recovery:
ui_url: <http://127.0.0.1:4455/recovery>
verification:
ui_url: <http://127.0.0.1:4455/verification>
methods:
password:
enabled: true
identity:
default_schema_id: default
schemas:
- id: default
url: file:///etc/config/kratos/identity-schemas/tpp.schema.json
courier:
templates:
verification:
valid:
email:
body:
html: file:///etc/config/kratos/email-templates/verification/body.html.gotmpl
plaintext: file:///etc/config/kratos/email-templates/verification/body.plaintext.gotmpl
subject: file:///etc/config/kratos/email-templates/verification/subject.gotmpl
delivery_strategy: smtp
smtp:
connection_uri: <smtps://sandbox-3>:***@smtp.devmail.email:2525/
from_address: <mailto:[email protected]|[email protected]>
from_name: FAPI Gatewaychilly-king-10285
07/29/2025, 9:21 PMchilly-king-10285
07/29/2025, 9:22 PMselfservice.flows.verification section. In your config, you have: yaml selfservice: flows: verification: ui_url: <http://127.0.0.1:4455/verification> However, you are missing the enabled: true and the use: code (or link) fields. According to the documentation, it should look like: yaml selfservice: flows: verification: enabled: true use: code # or 'link' ui_url: <http://127.0.0.1:4455/verification> [Address verification](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation) ### 2. Identity Schema Must Mark Email as Verifiable Your identity schema (referenced as tpp.schema.json) must mark the email trait as verifiable. For example: json "email": { "type": "string", "format": "email", "<http://ory.sh/kratos|ory.sh/kratos>": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" } } } [Address verification](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation) ### 3. Registration Flow and Hooks You have the show_verification_ui hook enabled after password registration, which is correct if you want to show the verification screen after registration. This is optional for sending the email, but required if you want to force users to verify before getting a session. By default, Ory will send the verification email if verification is enabled and the schema is correct, even without this hook. [Verification on sign up](https://www.ory.sh/docs/actions/require-verified-address#verification-on-sign-up) ### 4. Courier Configuration Your courier configuration looks correct, assuming your SMTP credentials and template paths are valid. ### 5. Common Pitfalls - If you do not see emails being sent, check the Kratos logs for errors related to the courier or SMTP delivery. - Make sure your SMTP server is reachable and credentials are correct. - Ensure your identity schema is loaded correctly and the verification property is present for the email trait. --- Summary of what you are missing: You need to add enabled: true and use: code (or link) to your selfservice.flows.verification section. Also, double-check your identity schema to ensure the email trait is marked as verifiable. If you make these changes and still do not receive verification emails, check the logs for courier or SMTP errors, and verify that your SMTP configuration is correct. For more details, see the [Ory Kratos Address Verification documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).boundless-ambulance-56979
07/30/2025, 6:13 PMchilly-king-10285
07/30/2025, 6:13 PMchilly-king-10285
07/30/2025, 6:13 PMcourier section. This config can be used in your YAML configuration file: yaml courier: delivery_strategy: http http: request_config: url: <https://mail-sender.example.com> method: POST body: base64://... # Base64-encoded Jsonnet template for the payload (see below) headers: Content-Type: application/json auth: type: basic_auth # or api_key config: user: my-username password: my-password # For api_key, use: # name: my-api-name # value: my-api-key-value # in: header # or cookie Notes: - The body field should contain a Base64-encoded Jsonnet template that defines the payload sent to your HTTP email provider. If omitted, a default template is used. - The auth section is optional and depends on your provider's requirements. Default Jsonnet template for the payload: jsonnet function(ctx) { recipient: ctx.recipient, template_type: ctx.template_type, to: if "template_data" in ctx && "to" in ctx.template_data then <http://ctx.template_data.to|ctx.template_data.to> else null, recovery_code: if "template_data" in ctx && "recovery_code" in ctx.template_data then ctx.template_data.recovery_code else null, recovery_url: if "template_data" in ctx && "recovery_url" in ctx.template_data then ctx.template_data.recovery_url else null, verification_url: if "template_data" in ctx && "verification_url" in ctx.template_data then ctx.template_data.verification_url else null, verification_code: if "template_data" in ctx && "verification_code" in ctx.template_data then ctx.template_data.verification_code else null, login_code: if "template_data" in ctx && "login_code" in ctx.template_data then ctx.template_data.login_code else null, registration_code: if "template_data" in ctx && "registration_code" in ctx.template_data then ctx.template_data.registration_code else null, subject: if "template_data" in ctx && "subject" in ctx.template_data then ctx.template_data.subject else null, body: if "template_data" in ctx && "body" in ctx.template_data then ctx.template_data.body else null } You can use this template as-is or customize it for your provider, then Base64-encode it and reference it in the body field. For more details and examples, see the official documentation: [Send emails using an HTTP server](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp#send-emails-using-an-http-server) and [HTTP based email delivery (self-hosted)](https://www.ory.sh/docs/kratos/self-hosted/email-http#configuration).boundless-ambulance-56979
07/30/2025, 6:15 PMchilly-king-10285
07/30/2025, 6:15 PMchilly-king-10285
07/30/2025, 6:15 PMyaml courier: delivery_strategy: http http: request_config: url: <https://api.your-email-provider.com/send> method: POST body: file:///etc/config/kratos/mail.api.request.jsonnet # or use <base64://ENCODED_JSONNET> headers: "Content-Type": "application/json" auth: type: api_key config: name: Authorization # The header or cookie name for the API key value: Bearer <API_KEY> # The value of your API key (prefix with 'Bearer ' if required by your provider) in: header # Use 'header' or 'cookie' as required by your provider Key points: - Replace url with your provider’s endpoint. - The body can be a file path or a base64-encoded Jsonnet template. If omitted, the default template is used. - Set the name, value, and in fields in the auth.config section according to your provider’s requirements. For example, for Sendgrid, you would use: yaml auth: type: api_key config: name: Authorization value: Bearer <API_KEY> in: header This configuration is directly supported and documented in the Ory Kratos docs for HTTP-based email delivery with API key authentication [HTTP based email delivery](https://www.ory.sh/docs/kratos/self-hosted/email-http#configuration) and [Send emails using an HTTP server](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp#send-emails-using-an-http-server). If you need a sample Jsonnet template for the body, let me know!boundless-ambulance-56979
07/30/2025, 6:30 PMchilly-king-10285
07/30/2025, 6:30 PMchilly-king-10285
07/30/2025, 6:30 PMbody parameter in your HTTP email delivery configuration, you provide a Jsonnet template that defines the payload sent to your email provider. This template can be referenced as a file (e.g., `file://path/to/template.jsonnet`) or as a base64-encoded string (e.g., <base64://ENCODED_JSONNET>). Ory Kratos will pass a ctx object to your Jsonnet function, containing relevant data for the email. Example Jsonnet template (mail.api.request.jsonnet): jsonnet function(ctx) { recipient: ctx.recipient, template_type: ctx.template_type, to: if "template_data" in ctx && "to" in ctx.template_data then <http://ctx.template_data.to|ctx.template_data.to> else null, recovery_code: if "template_data" in ctx && "recovery_code" in ctx.template_data then ctx.template_data.recovery_code else null, recovery_url: if "template_data" in ctx && "recovery_url" in ctx.template_data then ctx.template_data.recovery_url else null, verification_url: if "template_data" in ctx && "verification_url" in ctx.template_data then ctx.template_data.verification_url else null, verification_code: if "template_data" in ctx && "verification_code" in ctx.template_data then ctx.template_data.verification_code else null } How to reference it in your config: yaml courier: delivery_strategy: http http: request_config: url: <https://api.your-email-provider.com/send> method: POST body: file:///etc/config/kratos/mail.api.request.jsonnet headers: "Content-Type": "application/json" auth: type: api_key config: name: Authorization value: Bearer <API_KEY> in: header Alternatively, you can base64-encode your Jsonnet file and use: yaml body: base64://<BASE64_ENCODED_JSONNET> What fields are available in ctx? - `recipient`: The email address of the recipient. - `template_type`: The type of template (e.g., verification_valid). - `template_data`: An object with template-specific data (e.g., verification_url, recovery_code, etc.). For more details and a list of available fields, see the [official documentation](https://www.ory.sh/docs/kratos/self-hosted/email-http#configuration) and [Jsonnet reference](https://www.ory.sh/docs/kratos/reference/jsonnet).boundless-ambulance-56979
07/31/2025, 12:45 PMchilly-king-10285
07/31/2025, 12:45 PMchilly-king-10285
07/31/2025, 12:45 PMyaml selfservice: methods: totp: config: # The "issuer" is the name shown in the TOTP app (e.g., Google Authenticator) issuer: <http://ExampleIssuer.com|ExampleIssuer.com> enabled: true Key points: - The issuer value will be displayed in the authenticator app. - Make sure enabled: true is set. This configuration enables TOTP as a second-factor authentication method for your users. After enabling, users can pair their account with a TOTP app (like Google Authenticator) via the Ory Account Experience or your custom UI. For more details, see the official documentation: [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp) and [Self-hosted MFA configuration](https://www.ory.sh/docs/kratos/self-hosted/mfa).boundless-ambulance-56979
07/31/2025, 1:26 PMchilly-king-10285
07/31/2025, 1:26 PMchilly-king-10285
07/31/2025, 1:26 PMyaml selfservice: methods: totp: config: issuer: <http://ExampleIssuer.com|ExampleIssuer.com> enabled: true This enables TOTP as a second factor and sets the issuer name that appears in authenticator apps [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp). ### 2. Enforce MFA after login To require users to complete TOTP after logging in, set the required Authenticator Assurance Level (AAL) for sessions and/or sensitive flows. For example, to require the highest available AAL (which includes TOTP if configured) for accessing account settings: yaml selfservice: flows: settings: required_aal: highest_available session: whoami: required_aal: highest_available With this configuration, after a user logs in with their password, they will be prompted for TOTP if they have it configured. If not, they can proceed with just their password. If you want to require TOTP for all users who have it set up, this is the recommended approach [Configure multi-factor authentication in Ory Kratos](https://www.ory.sh/docs/kratos/self-hosted/mfa). ### 3. How it works in practice - User logs in with their first factor (e.g., password). - If the session or flow requires aal2 (second factor), and the user has TOTP configured, Ory will prompt for the TOTP code. - Upon successful TOTP entry, the session is upgraded to aal2. For more on step-up authentication and enforcing TOTP after login, see [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication). --- Summary: Enable TOTP in selfservice.methods, and set required_aal: highest_available for the relevant flows or session. This will prompt users for TOTP after a successful login if they have TOTP configured.boundless-ambulance-56979
07/31/2025, 1:27 PMboundless-ambulance-56979
07/31/2025, 1:27 PMchilly-king-10285
07/31/2025, 1:27 PMchilly-king-10285
07/31/2025, 1:27 PMyaml selfservice: methods: totp: config: # This is the name that will appear in Google Authenticator issuer: <http://ExampleIssuer.com|ExampleIssuer.com> enabled: true flows: settings: required_aal: highest_available session: whoami: required_aal: highest_available How it works: - After a user logs in with their password, if they have TOTP (e.g., Google Authenticator) configured, Ory will prompt them to enter the code from their app. - The issuer field is what users will see in Google Authenticator when they pair their account. - The required_aal: highest_available setting ensures that users must complete the second factor (TOTP) for sensitive actions or to upgrade their session to AAL2. This setup is directly supported and described in the Ory documentation for TOTP and step-up authentication [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp) [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication). If you want TOTP to be required immediately after login for all users who have it configured, this is the recommended approach. If a user does not have TOTP set up, they will not be prompted for it until they do so. If you need to customize which flows require TOTP, adjust the required_aal setting under the relevant flow (e.g., login, settings, etc.) as shown above.boundless-ambulance-56979
07/31/2025, 6:39 PMchilly-king-10285
07/31/2025, 6:39 PMchilly-king-10285
07/31/2025, 6:39 PMdeleteIdentityCredentials method, which requires authentication with an Ory access token. This operation is typically performed via the admin API and is not a self-service flow exposed to end users by default. Here’s what the knowledge sources state: > To perform this operation, you must be authenticated by means of one of the following methods: oryAccessToken [Kratos API Reference](https://raw.githubusercontent.com/ory/kratos/master/spec/api.json) The documentation does not provide a full example of the API call, but the process generally involves: - Authenticating as an admin or with appropriate permissions. - Calling the relevant admin API endpoint to delete the identity. If you need to delete an account via the Ory Console or CLI, you would typically use the admin interface or CLI commands to manage identities, but the knowledge sources do not provide explicit step-by-step instructions for this process. Summary: Account deletion is performed via the admin API using an authenticated request. The knowledge sources do not provide a detailed example, but you must use an Ory access token and the appropriate API endpoint to delete an identity.boundless-ambulance-56979
08/02/2025, 6:13 AMchilly-king-10285
08/02/2025, 6:13 AMchilly-king-10285
08/02/2025, 6:13 AMyaml selfservice: methods: totp: config: issuer: <http://ExampleIssuer.com|ExampleIssuer.com> # This is the name shown in Google Authenticator enabled: true This enables TOTP (Google Authenticator) for your project and sets the issuer name that appears in the authenticator app [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp). 2. Enforce MFA after login: Set the required Authenticator Assurance Level (AAL) to require the highest available factor (which includes TOTP if configured): yaml session: whoami: required_aal: highest_available With this configuration, after a user logs in with their password, Ory Kratos will check if the user has TOTP configured. If so, the user will be prompted for their TOTP code (e.g., from Google Authenticator) before the session is fully established at AAL2. If the user does not have TOTP set up, they will not be prompted for it. How it works: - User enters their username and password (normal login). - If TOTP is enabled and configured for the user, Ory will prompt for the TOTP code after the password is verified. - Upon successful TOTP entry, the user gains access with an AAL2 session. This approach is described in the official documentation for step-up authentication and TOTP configuration [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication) [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp). If you want to require TOTP only for specific flows (e.g., only for settings changes), adjust the required_aal setting under the relevant flow (such as selfservice.flows.settings.required_aal). Note: The knowledge sources do not mention a way to force TOTP for all users regardless of whether they have it configured, nor do they describe a way to force TOTP enrollment immediately after registration. Users must enable TOTP themselves in their account settings after registration [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp). --- Summary: Enable TOTP and set session.whoami.required_aal: highest_available to prompt for a Google Authenticator challenge after normal login if the user has TOTP configured.boundless-ambulance-56979
08/02/2025, 6:34 AMchilly-king-10285
08/02/2025, 6:34 AMchilly-king-10285
08/02/2025, 6:34 AMboundless-ambulance-56979
10/12/2025, 8:22 AM{
"error": {
"id": "security_csrf_violation",
"code": 403,
"status": "Forbidden",
"reason": "Please retry the flow and optionally clear your cookies. The request was rejected to protect you from Cross-Site-Request-Forgery (CSRF) which could cause account takeover, leaking personal information, and other serious security issues.",
"details": {
"docs": "<https://www.ory.sh/kratos/docs/debug/csrf>",
"hint": "The anti-CSRF cookie was found but the CSRF token was not included in the HTTP request body (csrf_token) nor in the HTTP Header (X-CSRF-Token).",
"reject_reason": "The HTTP Cookie Header was set and a CSRF token was sent but they do not match. We recommend deleting all cookies for this domain and retrying the flow."
},
"message": "the request was rejected to protect you from Cross-Site-Request-Forgery"
}
}
after redirecting to my app to check verification state and process with onboardingchilly-king-10285
10/12/2025, 8:22 AMchilly-king-10285
10/12/2025, 8:22 AM"error": { "id": "security_csrf_violation", ... "reason": "Please retry the flow and optionally clear your cookies. The request was rejected to protect you from Cross-Site-Request-Forgery (CSRF)...", "details": { ... "hint": "The anti-CSRF cookie was found but the CSRF token was not included in the HTTP request body (csrf_token) nor in the HTTP Header (X-CSRF-Token).", "reject_reason": "The HTTP Cookie Header was set and a CSRF token was sent but they do not match. We recommend deleting all cookies for this domain and retrying the flow." } } indicates a CSRF protection failure. This typically happens when the CSRF token in the request does not match the anti-CSRF cookie, or one of them is missing. Common causes and solutions based on the knowledge sources: 1. Cookies and CSRF Token Mismatch: This can occur if the verification link is opened in a different browser, incognito window, or if cookies were cleared or not sent with the request. Ory Kratos expects the CSRF cookie and the CSRF token (from the form or header) to match. If they do not, the request is rejected for security reasons [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf). 2. Public Suffix Domains: If you are using a domain that is on the [public suffix list](https://publicsuffix.org/), cookies may not be set correctly, leading to CSRF errors. Use a subdomain that is not on the PSL, such as <http://app.myftp.org|app.myftp.org> instead of <http://myftp.org|myftp.org> [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf). 3. Mixing Domains or Inconsistent URLs: Make sure you are not mixing localhost and 127.0.0.1, or using different subdomains, as cookies are domain-specific. Use the same domain consistently across your app and Ory Kratos [CSRF debug](https://www.ory.sh/docs/kratos/debug/csrf). 4. Flow Handling: When handling verification flows, ensure you are forwarding the Cookie header from the client when fetching the flow, especially in server-side applications. Without forwarding the Cookie header, you can't fetch the flow due to a security error [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf). 5. How to Fix: - Ask the user to retry the flow and clear cookies for the domain if necessary. - Ensure the verification link is opened in the same browser and context where the registration was performed. - Double-check your domain and cookie settings in the Kratos config. - If you are using a custom UI, make sure to include the CSRF token from the flow in your form submissions. For more details, see the [Ory Kratos CSRF troubleshooting guide](https://www.ory.sh/kratos/docs/debug/csrf). --- Summary: This error is caused by a mismatch or absence of the CSRF token and cookie. Ensure the verification link is opened in the same browser session, cookies are not blocked or cleared, and your domain configuration is correct. If the problem persists, clear cookies and retry the flow [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf).boundless-ambulance-56979
10/16/2025, 8:01 PMchilly-king-10285
10/16/2025, 8:01 PMchilly-king-10285
10/16/2025, 8:02 PMAccess-Control-Allow-Origin header for Ory Kratos, you need to configure the CORS settings in your Kratos configuration file under the serve.public.cors.allowed_origins section. This determines which origins are allowed to make cross-origin requests to your Kratos public API. Here is an example configuration: yaml serve: public: cors: enabled: true allowed_origins: - <https://example.org> - https://*.<http://example.org|example.org> allowed_methods: - POST - GET - PUT - PATCH - DELETE allowed_headers: - Authorization - Cookie - Content-Type exposed_headers: - Content-Type - Set-Cookie In this example, the allowed_origins list sets the values for the Access-Control-Allow-Origin header. You can specify exact origins or use wildcards for subdomains (e.g., https://*.<http://example.org|example.org>). For more details, see the official documentation: [Cross-origin resource sharing (CORS) settings](https://www.ory.com/docs/kratos/guides/setting-up-cors) and [Configure cross-origin resource sharing (CORS)](https://www.ory.com/docs/hydra/guides/cors).boundless-ambulance-56979
10/16/2025, 8:16 PMchilly-king-10285
10/16/2025, 8:16 PMchilly-king-10285
10/16/2025, 8:16 PM/self-service/logout/browser endpoint to generate a logout URL associated with the current session cookie. 2. Complete the Logout: Redirect the user to the generated logout URL (which points to /self-service/logout?token=...). This will invalidate the session and remove the session cookie. You can also specify a return_to parameter to redirect the user after logout. Example (Express.js): js // Create logout route app.get("/logout", async (req, res) => { try { // Create a logout flow const { logout_url } = await ory.createBrowserLogoutFlow({ cookie: req.header("cookie"), }) // Redirect to logout URL res.redirect(logout_url) } catch (err) { res.redirect("/") } }) After successful logout, the user is redirected to the specified return URL, the session is invalidated, and the session cookie is removed [Sign out](https://www.ory.com/docs/identities/get-started/sign-out) [Logout flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#logout-flow). --- ### 2. Native/API Logout Flow (Ory Kratos) For native or API clients (such as mobile apps), use the session token to log out: - Call the /self-service/logout/api endpoint with the session token in the request body. Example (TypeScript): ts await frontend.performNativeLogout({ performNativeLogoutBody: { session_token: token, }, }) This revokes the session token and logs out the user [Native API applications](https://www.ory.com/docs/kratos/self-service/flows/user-logout#native-api-applications). --- ### 3. OpenID Connect/OAuth2 Logout (Ory Hydra) For OIDC/OAuth2, Ory supports: - Front-Channel Logout: Redirects the browser to a registered frontchannel_logout_uri after logout. - Back-Channel Logout: Sends a POST request with a logout_token to a registered backchannel_logout_uri when the user logs out. To initiate logout, call the /oauth2/sessions/logout endpoint. You can use parameters like post_logout_redirect_uri, state, and id_token_hint to control the redirect and state after logout [OpenID Connect logout](https://www.ory.com/docs/oauth2-oidc/oidc-logout#redirect-after-logout). --- ### Summary Table | Application Type | Endpoint | Method | Notes | |------------------|------------------------------------------|----------|-----------------------------------------------------------------------| | Browser | /self-service/logout/browser | GET | Generates logout URL, then redirect user to it | | Browser | /self-service/logout | GET | Completes logout with token from previous step | | Native/API | /self-service/logout/api | DELETE | Send session token in body | | OIDC/OAuth2 | /oauth2/sessions/logout | GET | Initiates OIDC/OAuth2 logout, supports redirect and state parameters | --- For more details and code samples, see the [Ory Kratos Logout documentation](https://www.ory.com/docs/kratos/self-service/flows/user-logout) and [Ory OAuth2 & OpenID Connect logout documentation](https://www.ory.com/docs/oauth2-oidc/oidc-logout).boundless-ambulance-56979
10/18/2025, 2:53 PMchilly-king-10285
10/18/2025, 2:53 PMchilly-king-10285
10/18/2025, 2:53 PMafter verification flow in your Kratos configuration. This webhook will be called when a user successfully verifies their account. Example configuration: yaml selfservice: flows: verification: enabled: true use: code # or 'link' after: hooks: - hook: web_hook config: url: <https://your-backend.com/webhooks/verification-success> method: POST body: <base64://ENCODED_JSONNET> # Optional: customize the payload auth: type: api_key # Optional: if your backend requires authentication config: name: Authorization value: Bearer YOUR_API_KEY in: header ### 2. Customize the Webhook Payload (Optional) You can use a Jsonnet template to customize the data sent to your backend. For example: verification-webhook.jsonnet: jsonnet function(ctx) { user_id: ctx.identity.id, email: ctx.identity.verifiable_addresses[0].value, verified: ctx.identity.verifiable_addresses[0].verified, verification_status: ctx.identity.verifiable_addresses[0].status } Base64-encode this file and reference it in the body field: bash cat verification-webhook.jsonnet | base64 | pbcopy Then update your config: yaml body: <base64://ENCODED_JSONNET> ### 3. Handle the Webhook in Your Backend Your backend should listen for POST requests at the configured endpoint (e.g., /webhooks/verification-success) and process the verification event. The webhook payload will include the identity data, including the verified status. Example payload received by your backend: json { "user_id": "abc123", "email": "[email protected]", "verified": true, "verification_status": "completed" } ### 4. Apply the Configuration Update your Ory Identities configuration using the Ory CLI: bash ory update identity-config --project <project-id> --workspace <workspace-id> --file updated_config.yaml --- ### Alternative: Use Live Event Streams If you need real-time notifications for verification events, you can also use Ory Live Event Streams. The VerificationSucceeded event is emitted when a user successfully verifies their account [Event names](https://www.ory.com/docs/actions/live-events#event-names). --- ### Summary Configure a webhook in the after verification flow to notify your backend when a user verifies their account. Use Jsonnet to customize the payload if needed, and handle the webhook in your backend to process the verification event. For more details, see: - [Trigger custom logic with webhooks](https://www.ory.com/docs/guides/integrate-with-ory-cloud-through-webhooks) - [Configure hooks](https://www.ory.com/docs/kratos/hooks/configure-hooks) - [Live event streams](https://www.ory.com/docs/actions/live-events)boundless-ambulance-56979
10/18/2025, 11:15 PMchilly-king-10285
10/18/2025, 11:15 PMchilly-king-10285
10/18/2025, 11:15 PMafter configuration at the wrong level in your selfservice.flows section. Specifically, you have: yaml selfservice: flows: after: default_browser_return_url: <http://localhost:3000/main/overview/> This is not allowed. The after configuration should be nested inside specific flows (like registration, verification, etc.), not at the top level of selfservice.flows. ### How to Fix Remove the top-level after configuration and place it inside the appropriate flow. For example, if you want to set a default redirect after registration, it should be: yaml selfservice: flows: registration: after: default_browser_return_url: <http://localhost:3000/main/overview/> ### Corrected Configuration Structure Here's the corrected structure based on your config: yaml selfservice: flows: error: ui_url: <https://www.ory.sh/kratos/docs/fallback/error> login: lifespan: 1h ui_url: <http://localhost:3000/auth/login> recovery: enabled: false lifespan: 1h notify_unknown_recipients: false ui_url: <http://localhost:3000/auth/recovery> use: code registration: enabled: true lifespan: 1h login_hints: false ui_url: <http://localhost:3000/auth/register> after: default_browser_return_url: <http://localhost:3000/main/overview/> # Move here if needed code: hooks: - hook: session settings: lifespan: 1h privileged_session_max_age: 1h required_aal: highest_available ui_url: <http://localhost:3000/auth/settings> verification: enabled: true lifespan: 10m notify_unknown_recipients: false ui_url: <http://localhost:3000/account/verify> use: link after: default_browser_return_url: <http://localhost:3000/onboarding> hooks: - hook: web_hook config: url: <http://localhost:8080/api/v1/account/webhook/email-verification> method: POST body: file:///etc/config/kratos/hooks/verification.jsonnet ### Key Points - The after configuration must be inside a specific flow (e.g., registration, verification), not at the top level of selfservice.flows. - Each flow can have its own after section with hooks and redirect URLs. For more details on configuring hooks and flows, see the [Ory Kratos hooks documentation](https://www.ory.com/docs/kratos/hooks/configure-hooks).boundless-ambulance-56979
10/18/2025, 11:24 PMdsn: <cockroach://root@cockroach:26257/defaultdb?sslmode=disable>
serve:
public:
base_url: <http://localhost:4433/>
cors:
enabled: true
allowed_origins:
- <http://localhost:3000>
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Content-Type
- Cookie
exposed_headers:
- Content-Type
- Set-Cookie
allow_credentials: true
admin:
base_url: <http://localhost:4434/>
selfservice:
default_browser_return_url: <http://localhost:3000/main/overview>
flows:
login:
ui_url: <http://localhost:3000/auth/login>
after:
default_browser_return_url: <http://localhost:3000/main/overview>
registration:
enabled: true
ui_url: <http://localhost:3000/auth/register>
after:
code:
hooks:
- hook: session
settings:
ui_url: <http://localhost:3000/auth/settings>
recovery:
ui_url: <http://localhost:3000/auth/recovery>
verification:
enabled: true
ui_url: <http://localhost:3000/account/verify>
lifespan: 10m
use: link
after:
default_browser_return_url: <http://localhost:3000/onboarding>
hooks:
- hook: web_hook
config:
url: <http://localhost:8080/api/v1/account/webhook/email-verification>
method: POST
body: file:///etc/config/kratos/hooks/verification.jsonnet
methods:
password:
enabled: true
link:
enabled: true
identity:
default_schema_id: default
schemas:
- id: default
url: file:///etc/config/kratos/identity-schemas/user.schema.json
courier:
delivery_strategy: smtp
smtp:
connection_uri: "<smtps://resend:[email protected]:465>"
from_address: "[email protected]"
template_override_path: /etc/config/kratos/email-templates
templates:
verification:
valid:
email:
body:
html: file:///etc/config/kratos/email-templates/verification/body.html.gotmplboundless-ambulance-56979
10/18/2025, 11:24 PMdsn: <cockroach://root@cockroach:26257/defaultdb?sslmode=disable>
serve:
public:
base_url: <http://localhost:4433/>
cors:
enabled: true
allowed_origins:
- <http://localhost:3000>
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Content-Type
- Cookie
exposed_headers:
- Content-Type
- Set-Cookie
allow_credentials: true
admin:
base_url: <http://localhost:4434/>
selfservice:
default_browser_return_url: <http://localhost:3000/main/overview>
flows:
login:
ui_url: <http://localhost:3000/auth/login>
after:
default_browser_return_url: <http://localhost:3000/main/overview>
registration:
enabled: true
ui_url: <http://localhost:3000/auth/register>
after:
code:
hooks:
- hook: session
settings:
ui_url: <http://localhost:3000/auth/settings>
recovery:
ui_url: <http://localhost:3000/auth/recovery>
verification:
enabled: true
ui_url: <http://localhost:3000/account/verify>
lifespan: 10m
use: link
after:
default_browser_return_url: <http://localhost:3000/onboarding>
hooks:
- hook: web_hook
config:
url: <http://localhost:8080/api/v1/account/webhook/email-verification>
method: POST
body: file:///etc/config/kratos/hooks/verification.jsonnet
methods:
password:
enabled: true
link:
enabled: true
identity:
default_schema_id: default
schemas:
- id: default
url: file:///etc/config/kratos/identity-schemas/user.schema.json
courier:
delivery_strategy: smtp
smtp:
connection_uri: "<smtps://resend:[email protected]:465>"
from_address: "[email protected]"
template_override_path: /etc/config/kratos/email-templates
templates:
verification:
valid:
email:
body:
html: file:///etc/config/kratos/email-templates/verification/body.html.gotmplchilly-king-10285
10/18/2025, 11:24 PMchilly-king-10285
10/18/2025, 11:25 PMafter configuration at the wrong level in your selfservice.flows section. Looking at your config, you have: yaml selfservice: flows: login: ui_url: <http://localhost:3000/auth/login> after: default_browser_return_url: <http://localhost:3000/main/overview> This is not allowed. The after configuration cannot be a direct child of selfservice.flows. It must be nested inside specific flows (like login, registration, etc.). ### How to Fix Remove the standalone after block and place it inside the login flow (or whichever flow you want to configure). Here's the corrected configuration: yaml dsn: <cockroach://root@cockroach:26257/defaultdb?sslmode=disable> serve: public: base_url: <http://localhost:4433/> cors: enabled: true allowed_origins: - <http://localhost:3000> allowed_methods: - POST - GET - PUT - PATCH - DELETE - OPTIONS allowed_headers: - Authorization - Content-Type - Cookie exposed_headers: - Content-Type - Set-Cookie allow_credentials: true admin: base_url: <http://localhost:4434/> selfservice: default_browser_return_url: <http://localhost:3000/main/overview> flows: login: ui_url: <http://localhost:3000/auth/login> after: default_browser_return_url: <http://localhost:3000/main/overview> registration: enabled: true ui_url: <http://localhost:3000/auth/register> after: code: hooks: - hook: session settings: ui_url: <http://localhost:3000/auth/settings> recovery: ui_url: <http://localhost:3000/auth/recovery> verification: enabled: true ui_url: <http://localhost:3000/account/verify> lifespan: 10m use: link after: default_browser_return_url: <http://localhost:3000/onboarding> hooks: - hook: web_hook config: url: <http://localhost:8080/api/v1/account/webhook/email-verification> method: POST body: file:///etc/config/kratos/hooks/verification.jsonnet methods: password: enabled: true link: enabled: true identity: default_schema_id: default schemas: - id: default url: file:///etc/config/kratos/identity-schemas/user.schema.json courier: delivery_strategy: smtp smtp: connection_uri: "<smtps://resend:[email protected]:465>" from_address: "[email protected]" template_override_path: /etc/config/kratos/email-templates templates: verification: valid: email: body: html: file:///etc/config/kratos/email-templates/verification/body.html.gotmpl ### Key Changes - Moved the after block from being a direct child of selfservice.flows to inside the login flow. - Each flow (login, registration, verification, etc.) can have its own after section with hooks and redirect URLs. This should resolve the validation error. For more details on configuring flows and redirects, see the [Ory Kratos browser redirect documentation](https://www.ory.com/docs/kratos/concepts/browser-redirect-flow-completion).boundless-ambulance-56979
11/16/2025, 9:46 PMchilly-king-10285
11/16/2025, 9:46 PMchilly-king-10285
11/16/2025, 9:46 PMboundless-ambulance-56979
11/17/2025, 6:17 PMchilly-king-10285
11/17/2025, 6:17 PMchilly-king-10285
11/17/2025, 6:18 PMbash ory patch project <your-project-id> \ --replace '/cors_public/enabled=true' \ --replace '/cors_public/origins=["https://*.foobar.ory"]' [Set up CORS for Ory Network](https://www.ory.com/docs/guides/cors) ### CORS Configuration Options The Ory Network API supports configuring: - `cors_public`: CORS settings for public endpoints - `cors_admin`: CORS settings for admin endpoints Each has: - `enabled`: Whether CORS is enabled for the endpoint - `origins`: The allowed origins. You can use wildcards in subdomains (e.g., https://*.<http://example.com|example.com>) [Ory API Reference](https://www.ory.sh/docs/reference/api) ### Important Restrictions - Ory Network allows up to 50 origins - You cannot set CORS origins to *, null, or localhost/127.0.0.1 - You can use wildcard subdomains like https://*.foobar.ory - For local development with localhost/127.0.0.1, use [Ory Tunnel](https://www.ory.com/docs/guides/cli/proxy-and-tunnel) instead [Set up CORS for Ory Network](https://www.ory.com/docs/guides/cors) ### CORS on Custom Domains If you're using custom domains, you can configure specific CORS settings for them. See the [Custom Domains documentation](https://www.ory.com/docs/guides/custom-domains#cors) for details. ### Supported Endpoints All public endpoints apply the global CORS settings. For OAuth2, per-client CORS settings are also supported if CORS is enabled globally [Set up CORS for Ory Network](https://www.ory.com/docs/guides/cors).boundless-ambulance-56979
11/24/2025, 11:15 PMchilly-king-10285
11/24/2025, 11:15 PMchilly-king-10285
11/24/2025, 11:15 PMcors_public) – applies to https://<project-slug>.<http://projects.oryapis.com|projects.oryapis.com>.[\[CORS guide\]](https://www.ory.com/docs/guides/cors) 2. Custom domain CORS – applies if you front Ory with your own domain (e.g. <https://auth.example.com>).[\[Custom domains\]](https://www.ory.com/docs/guides/custom-domains#cors) You are calling: > <https://kind-poincare-9pwd3nyapa.projects.oryapis.com/>... from > <https://app.useinvolt.com> So you must configure global project CORS and allow <https://app.useinvolt.com>. ### 1. Enable CORS for your project Use the Ory CLI and set `cors_public`: bash ory patch project <your-project-id> \ --replace '/cors_public/enabled=true' \ --replace '/cors_public/origins=["<https://app.useinvolt.com>"]' You can also add more origins up to the 50-origin limit.[\[CORS guide\]](https://www.ory.com/docs/guides/cors) Notes: - Origins must be exact (<https://app.useinvolt.com>, not just the domain). - *, null, localhost, 127.0.0.1 are not allowed as origins; use a real domain or wildcards like https://*.<http://example.com|example.com>.[\[CORS guide\]](https://www.ory.com/docs/guides/cors) ### 2. Make sure you don’t mix CORS configs If you also use a custom domain for Ory, configure CORS either: - on the custom domain entry or - globally, but don’t stack conflicting settings; this can cause CORS to appear “not working”.[\[Custom-domain CORS note\]](https://github.com/ory/network/issues/427) ### 3. Call the right URL and send credentials From your SPA: - Use the project slug base URL you configured CORS for. - Send cookies with withCredentials: true / credentials: "include" when needed, as described in the cookie/CORS docs.[\[Cookie model\]](https://www.ory.com/docs/security-model#cross-origin-http-cookies) Example (fetch): js fetch("<https://kind-poincare-9pwd3nyapa.projects.oryapis.com/self-service/login/flows?id=...>", { credentials: "include", }) If, after setting cors_public as above, you still don’t see an Access-Control-Allow-Origin header for that origin, double‑check that: - The origin string in cors_public.origens exactly matches <https://app.useinvolt.com>. - You patched the correct project ID.boundless-ambulance-56979
11/24/2025, 11:18 PMboundless-ambulance-56979
11/24/2025, 11:18 PMchilly-king-10285
11/24/2025, 11:18 PMchilly-king-10285
11/24/2025, 11:19 PM<https://auth.useinvolt.com> → CNAME → <http://kind-poincare-9pwd3nyapa.projects.oryapis.com|kind-poincare-9pwd3nyapa.projects.oryapis.com>), but your SPA is still calling the project slug URL directly: - Browser origin: <https://app.useinvolt.com> - Request URL: <https://kind-poincare-9pwd3nyapa.projects.oryapis.com/sessions/whoami> - Error: no Access-Control-Allow-Origin Two important points from the docs: 1. When you add a custom domain, that domain becomes your SDK Configuration URL – it’s the base URL your frontend should call. [[Custom domains](https://www.ory.com/docs/guides/custom-domains)] 2. CORS for custom domains is configured on the custom domain itself (and is enabled by default, but you must add allowed origins). [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] So right now: - Your browser origin <https://app.useinvolt.com> is making CORS requests against the slug domain, which uses the global project CORS config. - But you actually want to use the custom domain (<https://auth.useinvolt.com>) with its own CORS settings. ### Fix 1. Call the custom domain from your SPA Change all frontend base URLs / SDK config to: ts const ory = new FrontendApi( new Configuration({ basePath: "<https://auth.useinvolt.com>", baseOptions: { withCredentials: true, }, }), ) So your whoami call becomes: js fetch("<https://auth.useinvolt.com/sessions/whoami>", { credentials: "include", }) 2. Configure CORS on the custom domain In Ory Console: - Go to Branding → Custom domains → auth.useinvolt.com - In the CORS section, add: - <https://app.useinvolt.com> (No *, null, or localhost – these are not allowed, but wildcard subdomains like https://*.<http://example.com|example.com> are. [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)]) 3. Avoid double / conflicting CORS configs For most setups with a custom domain you only need the custom domain CORS, not both custom-domain CORS and global project cors_public. Having both can be confusing and has caused issues for others. [[CORS not working custom domain](https://github.com/ory/network/issues/427)] After: - SPA origin: <https://app.useinvolt.com> - Ory base URL: <https://auth.useinvolt.com> - CORS origin configured on the custom domain for <https://app.useinvolt.com> the Access-Control-Allow-Origin header should be present and the CORS error should go away.boundless-ambulance-56979
11/24/2025, 11:30 PMchilly-king-10285
11/24/2025, 11:30 PMchilly-king-10285
11/24/2025, 11:30 PMhttp Access-Control-Allow-Origin: <https://app.useinvolt.com>,<https://app.useinvolt.com> This exact problem has occurred before when both: 1. Global / service CORS is configured, and 2. Custom domain CORS is also configured for the same origin. In Ory Network this was seen when Hydra’s public_cors (or cors_public) config and the custom domain CORS both listed the same origin, causing duplicated CORS headers.[[Invalid header](https://github.com/ory/network/issues/316)][[Custom-domain CORS note](https://github.com/ory/network/issues/427)] From the investigation in that issue: > you are using the new CORS settings for Ory Hydra, and also set the CORS domain in the custom hostname. > That causes the domain to be duplicated. To fix that, remove the public_cors setting or remove the CORS domain from the custom hostname config.[[Invalid header](https://github.com/ory/network/issues/316)] For your setup: - Frontend origin: <https://app.useinvolt.com> - Ory custom domain: <https://auth.useinvolt.com> You likely have both: - Project‑level CORS (e.g. cors_public / service CORS) that includes <https://app.useinvolt.com>, and - Custom‑domain CORS (on <http://auth.useinvolt.com|auth.useinvolt.com>) that also includes <https://app.useinvolt.com>. So the browser gets CORS headers twice and merges them into a comma‑separated value. ### How to fix 1. Decide where you want to configure CORS for browser access: - Recommended with custom domains: use only the custom domain CORS on <http://auth.useinvolt.com|auth.useinvolt.com> and remove overlapping global/service CORS for that origin.[[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)][[CORS not working custom domain](https://github.com/ory/network/issues/427)] 2. In practice: - In the Ory Console, under your custom domain <http://auth.useinvolt.com|auth.useinvolt.com>, keep <https://app.useinvolt.com> in the CORS origins. - Remove <https://app.useinvolt.com> (and any wildcards that match it) from: - Global CORS settings (cors_public / cors_admin) or - Any per‑service CORS you might have configured for the same endpoints. 3. Wait a moment for config to propagate, then call: bash curl -v '<https://auth.useinvolt.com/sessions/whoami>' \ -H 'Origin: <https://app.useinvolt.com>' You should now see a single Access-Control-Allow-Origin: <https://app.useinvolt.com> header. If you’re unsure where the second CORS config is coming from, check: - Project‑level CORS settings in Ory Console / via ory get project. - CORS section on the custom domain in Branding → Custom domains.[[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)]boundless-ambulance-56979
11/25/2025, 5:51 PMchilly-king-10285
11/25/2025, 5:51 PMchilly-king-10285
11/25/2025, 5:52 PMtext Origin: <https://app.useinvolt.com> URL: <https://auth.useinvolt.com/>... That part is correct – keep all browser calls on <https://auth.useinvolt.com>, not on kind-poincare-…<http://projects.oryapis.com|projects.oryapis.com>. 2. CORS is only applied on API (XHR/fetch) endpoints, not on browser redirect endpoints The URL that is failing now is: text <https://auth.useinvolt.com/self-service/login?flow=>... This is the browser login URL, which is meant to be navigated to (changed window.location / normal link), not called via XHR/fetch. For browser flows, the SPA should: - First initialize the flow via the API endpoint (SDK does this): GET /self-service/login/browser or /self-service/login/api - Then redirect the browser to the returned ui.action URL, instead of using `fetch`/XHR for that URL.[\[Custom UI integration\]](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#login-flow) If you attempt to fetch the browser URL (/self-service/login?flow=...), the preflight hits an endpoint that does not send CORS headers and you get exactly the error you’re seeing. So at this point the problem is very likely not missing CORS config on the domain, but calling the wrong URL with XHR. ### What to change in your frontend In your SPA, make sure you: - Use the Ory Network Frontend API endpoints for XHR: - GET /self-service/login/browser (or /api variant) - GET /self-service/login/flows?id=... - POST to flow.ui.action when submitting the form - Only navigate to the browser URLs (no fetch on /self-service/login?flow=...): ts // correct: browser navigation window.location.href = "<https://auth.useinvolt.com/self-service/login/browser>" Do not: ts // wrong: causes CORS error await fetch("<https://auth.useinvolt.com/self-service/login?flow=...>") This pattern is the same as in the “Bring your own UI” docs: use the SDK / FrontendApi to talk to /self-service/*/browser or /self-service/*/api and render the returned flow JSON in your own UI; do not call the redirect URLs via XHR.[\[Custom UI login flow\]](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#login-flow) ### CORS config recap Given you removed the global CORS and only use the custom domain: - In Branding → Custom domains → auth.useinvolt.com, ensure CORS origins include exactly: text <https://app.useinvolt.com> Ory Network does not allow *, null, or localhost, but allows exact origins or wildcard subdomains; use an exact match here.[\[Network CORS guide\]](https://www.ory.com/docs/guides/cors)[\[Custom domains CORS\]](https://www.ory.com/docs/guides/custom-domains#cors) If you still see issues after switching away from fetch on /self-service/login?flow=..., the knowledge sources don’t provide more specific debugging steps for this exact combination; at that point I’d recommend posting the exact request/response headers to the Ory community or support.boundless-ambulance-56979
11/25/2025, 9:03 PMchilly-king-10285
11/25/2025, 9:03 PMchilly-king-10285
11/25/2025, 9:03 PMjson: cannot unmarshal string into Go struct field <http://SendEmailRequest.to|SendEmailRequest.to> of type []string So your service expects to to be an array of strings, but Ory is currently sending it as a single string. From the Ory email HTTP delivery docs, the default Jsonnet payload Ory generates for HTTP email providers is: jsonnet function(ctx) { recipient: ctx.recipient, template_type: ctx.template_type, to: if "template_data" in ctx && "to" in ctx.template_data then <http://ctx.template_data.to|ctx.template_data.to> else null, recovery_code: if "template_data" in ctx && "recovery_code" in ctx.template_data then ctx.template_data.recovery_code else null, recovery_url: if "template_data" in ctx && "recovery_url" in ctx.template_data then ctx.template_data.recovery_url else null, verification_url: if "template_data" in ctx && "verification_url" in ctx.template_data then ctx.template_data.verification_url else null, verification_code: if "template_data" in ctx && "verification_code" in ctx.template_data then ctx.template_data.verification_code else null, login_code: if "template_data" in ctx && "login_code" in ctx.template_data then ctx.template_data.login_code else null, registration_code: if "template_data" in ctx && "registration_code" in ctx.template_data then ctx.template_data.registration_code else null, subject: if "template_data" in ctx && "subject" in ctx.template_data then ctx.template_data.subject else null, body: if "template_data" in ctx && "body" in ctx.template_data then ctx.template_data.body else null } Here to is whatever Kratos puts into <http://ctx.template_data.to|ctx.template_data.to>, which is a string email address for verification/recovery templates.[\[HTTP payload\]](https://www.ory.com/docs/kratos/emails-sms/sending-emails-smtp#payload)[\[HTTP email config\]](https://www.ory.com/docs/kratos/self-hosted/email-http#configuration) That means the JSON Ory is posting to your /api/v1/email/send looks roughly like: json { "recipient": "[email protected]", "template_type": "verification_valid", "to": "[email protected]", "verification_url": "https://…", "subject": "…", "body": "…" } Your Go struct expects: go type SendEmailRequest struct { To []string `json:"to"` // ... } so it rejects the string. To fix this, override the Jsonnet body in your Kratos/Ory Network email HTTP config so to is an array: jsonnet function(ctx) { recipient: ctx.recipient, template_type: ctx.template_type, to: if "template_data" in ctx && "to" in ctx.template_data then [ <http://ctx.template_data.to|ctx.template_data.to> ] // wrap string in array else [], subject: if "template_data" in ctx && "subject" in ctx.template_data then ctx.template_data.subject else null, body: if "template_data" in ctx && "body" in ctx.template_data then ctx.template_data.body else null } Then reference this Jsonnet in your courier HTTP config (body: file://... or base64://...).[\[HTTP email config\]](https://www.ory.com/docs/kratos/self-hosted/email-http#configuration)[\[HTTP payload\]](https://www.ory.com/docs/kratos/emails-sms/sending-emails-smtp#payload) So: the object Ory sends is a JSON with to as a string; your service needs to as []string, so adjust the Jsonnet to wrap it in an array.boundless-ambulance-56979
11/29/2025, 10:38 PMchilly-king-10285
11/29/2025, 10:38 PMchilly-king-10285
11/29/2025, 10:38 PM410 Gone on self-service flows (login, registration, verification, recovery, etc.) is returned in a standard JSON error with an ID like self_service_flow_replaced, and may include a use_flow_id field that points to the replacement flow. [[getLoginFlow](https://www.ory.sh/docs/kratos/reference/api); [updateVerificationFlow](https://www.ory.sh/docs/kratos/reference/api)] In your case: - code: 410, id: "self_service_flow_replaced" → current flow is no longer usable. - use_flow_id: "98671a07-5644-4eec-b3eb-2d162955cd8b" → this is the new verification flow you must use. This typically happens when: - The flow has expired (check lifespan in selfservice.flows.verification), or - The server decided to issue a fresh flow (common if something about the original request is no longer consistent). ### What you should do in your UI / client When you receive this error during verification: 1. Read use_flow_id from the error. 2. Reload the flow using that ID, instead of the old one: http GET /self-service/verification/flows?id=98671a07-5644-4eec-b3eb-2d162955cd8b (or via the SDK’s getVerificationFlow / frontend.getVerificationFlow call). [[getRecoveryFlow](https://www.ory.sh/docs/kratos/reference/api); [getRegistrationFlow](https://www.ory.sh/docs/reference/api)] 3. Render the new flow in your UI and let the user continue. If you don’t handle this and keep calling your backend with the old flow ID, you’ll keep getting 410 self_service_flow_replaced. If you need concrete code for your specific stack (React/Next/etc.), share how you’re currently fetching the verification flow and I can show how to plug in the use_flow_id handling.boundless-ambulance-56979
11/29/2025, 10:44 PMchilly-king-10285
11/29/2025, 10:44 PMchilly-king-10285
11/29/2025, 10:44 PMself_service_flow_replaced (410) with a use_flow_id, you must stop using the old flow ID and fetch/render the new flow using that use_flow_id. The pattern is the same as in the docs for handling expired flows: when you see 410, redirect or refetch with a new flow ID. [[Create verification flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-verification-flow)] ### Client-side (React) example If you’re currently doing something like this for verification: ts frontend .getVerificationFlow({ id }) .then(({ data: flow }) => setFlow(flow)) .catch((err) => { // handle error }) extend the error handling to use use_flow_id when you see 410: ts frontend .getVerificationFlow({ id }) .then(({ data: flow }) => setFlow(flow)) .catch((err: AxiosError<any>) => { if (err.response?.status === 410) { const newId = err.response.data?.use_flow_id if (newId) { // refetch with the new flow id frontend .getVerificationFlow({ id: newId }) .then(({ data: flow }) => setFlow(flow)) } else { // fallback: start a brand new flow by redirecting window.location.href = "/account/verify" // your verification UI URL } } else { // other error handling } }) The same idea applies when submitting a flow (updateVerificationFlow): ts frontend .updateVerificationFlow({ flow: flow.id, updateVerificationFlowBody: body }) .then(() => { /* success */ }) .catch((err: AxiosError<any>) => { if (err.response?.status === 410) { const newId = err.response.data?.use_flow_id if (newId) { // reload page / router to use ?flow=newId navigate(`/account/verify?flow=${newId}`, { replace: true }) } else { navigate("/account/verify", { replace: true }) } } // handle 400 (validation) etc. }) ### Server-side example (similar to docs) In the server-rendered example from the docs, when getVerificationFlow returns 410, they redirect to the init URL to create a new flow; your variant can also read use_flow_id if present. [[Create verification flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-verification-flow)] ts sdk .getVerificationFlow({ id: flow, cookie: req.header("cookie") }) .then(({ data: flow }) => { /* render */ }) .catch((err) => { if (err.response?.status === 410) { // start a fresh flow res.redirect(303, initFlowUrl) return } }) ### Summary - self_service_flow_replaced + use_flow_id means: throw away the old ID and use use_flow_id. - Practically: on 410, either: - refetch the flow with GET /self-service/.../flows?id=<use_flow_id>, or - redirect the user to your flow init URL (e.g. /verification) so a fresh flow is created.boundless-ambulance-56979
11/29/2025, 11:28 PMchilly-king-10285
11/29/2025, 11:28 PMchilly-king-10285
11/29/2025, 11:29 PMtext <https://auth.useinvolt.com/self-service/registration?flow=>... This is the browser redirect URL of the registration flow, not the JSON API endpoint. When you call it with `XMLHttpRequest`/`fetch`, the browser sends a CORS preflight, but that endpoint is not meant for XHR and does not send CORS headers, so you get: > No Access-Control-Allow-Origin header is present on the requested resource. From the self-service docs, the pattern is: - Use the browser/API endpoints for AJAX: - Initialize: GET /self-service/registration/browser with Accept: application/json for SPA. [[Registration AJAX](https://www.ory.com/docs/kratos/self-service/flows/user-registration#registration-for-client-side-ajax-browser-clients)] - Fetch flow: GET /self-service/registration/flows?id=<flow-id> - Submit: POST to flow.ui.action with credentials: "include" and CSRF token, all under the same top‑level domain and with CORS configured. [[Cookie security](https://www.ory.com/docs/security-model#cross-origin-http-cookies)] - Do not XHR the plain /self-service/registration?flow=... browser URL – that one is for normal browser navigation / redirects, not CORS/XHR. Given your setup: - Ory custom domain: <https://auth.useinvolt.com> - App origin: <https://app.useinvolt.com> - CORS for the custom domain is configured in the Ory Console under that custom domain entry. [[Custom-domain CORS](https://www.ory.com/docs/guides/custom-domains#cors)] What to change in your frontend: 1. Ensure you are calling flow APIs, not the redirect URL, from XHR. For example (using fetch): js // initialize flow fetch("<https://auth.useinvolt.com/self-service/registration/browser>", { credentials: "include", headers: { Accept: "application/json" }, }).then(r => r.json()).then(flow => { // use flow.ui.action, flow.id, etc. }) 2. When you need to navigate the user (e.g. to hosted UI), use window.location or a link to /self-service/registration/browser, not `fetch`: js window.location.href = "<https://auth.useinvolt.com/self-service/registration/browser>" 3. Keep CORS origins on the custom domain set to exactly <https://app.useinvolt.com> (no *, null, or localhost). [[CORS guide](https://www.ory.com/docs/guides/custom-domains#cors)] If you still see this error while calling only the JSON registration flow endpoints (/self-service/registration/browser or /self-service/registration/flows), then the knowledge here doesn’t provide more detailed debugging steps for that combination, and you’d want to capture the full request/response headers and share them with Ory support or the community.boundless-ambulance-56979
11/29/2025, 11:40 PMchilly-king-10285
11/29/2025, 11:40 PMchilly-king-10285
11/29/2025, 11:41 PMcors_public / cors_admin), configured via CLI. [[CORS guide](https://www.ory.com/docs/guides/cors)] - Custom-domain CORS, configured on the custom domain entry in Console. [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] You wrote: > This allows my SPA at https://app.useinvolt.com to make AJAX calls … The CORS is properly configured at the Ory project level… But you are calling <https://auth.useinvolt.com> (a custom domain, CNAME to your project). With a custom domain, the docs are explicit: CORS for that hostname is configured on the custom domain, not on the slug / global project URL. [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] If you still have project‑level CORS enabled for the same origins and CORS set on the custom domain, you can get duplicated or conflicting headers (which you already hit earlier). The recommended pattern with a custom domain is: - Configure CORS only on the custom domain for browser clients that use that domain. - Avoid overlapping per‑project / per‑service CORS for the same host/origin pair. [[CORS not working for custom domain](https://github.com/ory/network/issues/427)] So: double‑check that <https://app.useinvolt.com> is listed under Branding → Custom domains → auth.useinvolt.com → CORS, and that you’re not also layering global cors_public for the same combination. ### 2. Endpoints that actually use CORS Your last error was for: text <https://auth.useinvolt.com/self-service/registration?flow=>... The docs describe these as browser redirect endpoints, not the JSON API endpoints. They are meant to be hit by normal browser navigation, not by XHR/fetch, and therefore they don’t participate in CORS the way your SPA expects. [[Registration flow – browser vs. AJAX](https://www.ory.com/docs/kratos/self-service/flows/user-registration#registration-for-client-side-ajax-browser-clients)] For SPAs, the documented pattern is: - Initialize with GET /self-service/registration/browser and Accept: application/json (or use the SDK helper that does this). - Fetch with GET /self-service/registration/flows?id=<flow-id>. - Submit by POSTing to flow.ui.action. [[Custom UI registration flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#registration-flow)] If at any point your code uses `fetch`/XHR directly against /self-service/registration?flow=... or /self-service/login?flow=... (instead of /self-service/*/flows?id=...), you’ll see exactly the preflight CORS failure you pasted, because those “browser” endpoints are not supposed to be used as CORS APIs. You say: > Flow Initialization: Using browser redirect to /self-service/*/browser > Flow Fetching: Using the SDK’s getLoginFlow / getRegistrationFlow > Flow Submission: Using the SDK’s submit* methods That is the pattern the docs describe. At this point, the logs show at least one request is still going to /self-service/registration?flow=... via XHR (the error you pasted), so either: - Some code path is still using `fetch`/XHR against that URL, or - A library/SDK wrapper is misconfigured and using the browser URL instead of the flow API URLs. From the available sources, there isn’t more detailed tracing for your concrete code, only the general requirements: - CORS for custom domains must be set on the custom domain entry, not just at “project level”. [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] - SPAs must use the JSON flow endpoints for XHR, not the ?flow= browser endpoints. [[Registration AJAX](https://www.ory.com/docs/kratos/self-service/flows/user-registration#registration-for-client-side-ajax-browser-clients)] If you’ve checked those and your SDK calls still cause CORS on …/self-service/registration?flow=…, the knowledge here doesn’t include more low‑level debugging steps; you’d need to inspect the actual network tab (request URLs and methods for each call) or share that with Ory support/community for deeper investigation.boundless-ambulance-56979
11/30/2025, 12:04 AMchilly-king-10285
11/30/2025, 12:04 AMchilly-king-10285
11/30/2025, 12:04 AMserve.public.cors) is part of the Kratos service config, which Ory Network does not let you control directly. On Ory Network: - Runtime / serve.*, cookies, TLS, etc. are managed by the platform and **cannot be changed**; they’re either ignored or only shown as effective values.[\[Config with CLI](https://www.ory.com/docs/guides/cli/config-with-cli#configure-projects); \[patch identity-config](https://www.ory.com/docs/cli/ory-patch-identity-config#ory-patch-identity-config)] - For CORS on Ory Network you must use the project‑level CORS (cors_public / cors_admin) and/or custom‑domain CORS, not serve.public.cors like in self‑hosted Kratos.[\[Network CORS guide](https://www.ory.com/docs/guides/cors); \[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] That’s why your patch: bash ory patch identity-config <project> \ --replace '/serve/public/cors/enabled=false' produces a big dump of the identity config plus warnings like: > Configuration key serve.* can not be set and will be ignored. Those warnings are exactly the platform telling you that serve.public.cors (and other serve.* keys) are not configurable on Ory Network and are being ignored.[\[patch identity-config](https://www.ory.com/docs/cli/ory-patch-identity-config#ory-patch-identity-config)] ### What you can actually do On Ory Network the supported ways to control CORS are: 1. Global project CORS (for the slug …<http://projects.oryapis.com|projects.oryapis.com> and also for services when not using custom domains): bash ory patch project <project-id> \ --replace '/cors_public/enabled=false' or to control origins: bash ory patch project <project-id> \ --replace '/cors_public/origins=["<https://app.useinvolt.com>"]' The cors_public / cors_admin schema is documented in the project APIs.[\[setProject](https://www.ory.sh/docs/reference/api#operation/updateProject); \[cors fields](https://www.ory.sh/docs/reference/api#operation/patchProject)] 2. Custom‑domain CORS (for <https://auth.useinvolt.com>): configure it in the Console under Branding → Custom domains → your domain → CORS, as described in the custom‑domains docs.[\[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] You cannot disable serve.public.cors via ory patch identity-config on Ory Network; that key is from the self‑hosted Kratos schema and is intentionally ignored in Network.boundless-ambulance-56979
11/30/2025, 8:40 AMchilly-king-10285
11/30/2025, 8:41 AMchilly-king-10285
11/30/2025, 8:41 AMcode to link in your Identities (Kratos) config. ### 1. Update the verification flow to use link In your identity config: yaml selfservice: methods: link: enabled: true config: lifespan: 15m # how long the verification link is valid flows: verification: enabled: true use: link # switch from 'code' to 'link' lifespan: 15m notify_unknown_recipients: false This follows the documented pattern where verification.use can be code or link, and the selected method controls how verification emails are sent. [[Address verification](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#configuration)] ### 2. Make sure your identity schema supports verification Your identity schema must still mark the email as verifiable (this doesn’t change between code and link): json "email": { "type": "string", "format": "email", "<http://ory.sh/kratos|ory.sh/kratos>": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" } } } [[Address verification](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#identity-schema)] ### 3. Registration behavior - Registration itself doesn’t care whether verification is code or `link`; it just triggers the verification flow when verification is enabled. - The method you set (use: link) controls how the verification email is sent and which templates are used: - For `link`: verification.valid / verification.invalid templates are used. - For `code`: verification_code.valid / verification_code.invalid. [[Email templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates)] If you’re requiring verification right after registration (via the show_verification_ui hook), that works with both strategies; you’re only changing the email method. [[Verification on sign up](https://www.ory.com/docs/actions/require-verified-address#verification-on-sign-up)] ### 4. Important note The docs mark magic links (link) as legacy and scheduled for removal, and recommend using code instead. [[Choosing the right strategy](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#choosing-the-right-strategy)]boundless-ambulance-56979
11/30/2025, 8:48 AMchilly-king-10285
11/30/2025, 8:48 AMchilly-king-10285
11/30/2025, 8:48 AMjsonnetsecure: ERROR: RUNTIME ERROR: Field does not exist: verified_at means that ctx.flow in your webhook Jsonnet does not have a field named verified_at, so Jsonnet throws at: jsonnet verified_at: ctx.flow.verified_at, In the webhook Jsonnet context, ctx contains the full flow object (including flow.id, flow.type, flow.ui, etc.), but the docs’ example payload for flows does not mention a verified_at field on the flow itself. Instead you see fields like expires_at, issued_at, request_url, type, ui, etc. [[Jsonnet templating](https://www.ory.com/docs/guides/integrate-with-ory-cloud-through-webhooks#jsonnet-templating)] Because that property doesn’t exist, any direct access (ctx.flow.verified_at) causes a runtime error in Jsonnet, which then surfaces as a 500. You have two options: ### 1. Remove verified_at if you don’t strictly need it Just drop that line: jsonnet function(ctx) { identity_id: ctx.identity.id, flow_id: ctx.flow.id, event: "verification.success", identity: { id: ctx.identity.id, schema_id: ctx.identity.schema_id, state: ctx.identity.state, traits: ctx.identity.traits, verifiable_addresses: ctx.identity.verifiable_addresses, }, } This will stop the Jsonnet runtime error. ### 2. Make access to verified_at safe / optional If you want to keep a timestamp-like field but it may not exist, wrap it in a conditional so Jsonnet doesn’t crash when the field is missing: jsonnet function(ctx) { identity_id: ctx.identity.id, flow_id: ctx.flow.id, event: "verification.success", verified_at: if "verified_at" in ctx.flow then ctx.flow.verified_at else null, identity: { id: ctx.identity.id, schema_id: ctx.identity.schema_id, state: ctx.identity.state, traits: ctx.identity.traits, verifiable_addresses: ctx.identity.verifiable_addresses, }, } Using if "field" in obj then obj.field else null is the recommended pattern from the Jsonnet reference to handle optional fields without runtime errors. [[Jsonnet optionality](https://www.ory.com/docs/kratos/reference/jsonnet#optionality)] --- So the fix is: don’t assume ctx.flow.verified_at exists; either remove it or guard it with an if "verified_at" in ctx.flow check.boundless-ambulance-56979
12/01/2025, 6:11 PMchilly-king-10285
12/01/2025, 6:11 PMchilly-king-10285
12/01/2025, 6:11 PMyaml courier: smtp: from_name: MyProject via Ory # or http: ... if using HTTP and your custom templates under courier.smtp.templates / courier.http as in the docs. [[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)] If no custom server is configured, your templates in the Console UI are ignored and defaults are used. --- ### 2. Customize the right verification template Which template is used depends on your verification method: - If selfservice.flows.verification.use: code → templates: - verification_code.valid - verification_code.invalid [[Email templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#email-templates)] - If use: link → templates: - verification.valid - verification.invalid [[Email templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#email-templates)] Your email content: > “Your verification code is: … Or click this link to verify: …” is the default code template. So: - Check your config: selfservice.flows.verification.use is most likely code. - In Branding → Email templates, make sure you edited Verification code / valid (and plaintext + HTML) – not just the link‑based variant. [[Account verification templates](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#email-templates)] --- ### 3. Ensure the templates are actually wired in config In Ory Network, the Console UI writes Base64 templates into the identity config under courier.smtp.templates. That section must exist and contain non‑empty bodies, e.g.: yaml courier: smtp: templates: verification_code: valid: email: body: html: "<base64://ENCODED_HTML>" plaintext: "<base64://ENCODED_TEXT>" If the method is unsupported (e.g. http:// or file:// URIs) or missing, Ory falls back to defaults. [[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)][[Self-hosted mail templates](https://www.ory.com/docs/kratos/self-hosted/mail-courier-templates)] --- ### 4. Why you still see the default So if you: - Have not configured a custom SMTP/HTTP courier → defaults are used. - Edited verification.valid instead of verification_code.valid while using use: code → defaults for code are used. - Misconfigured templates (unsupported URL instead of base64:// in Network) → Ory falls back to defaults. [[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)] --- Next steps 1. Confirm selfservice.flows.verification.use: code or link. 2. In Console, edit the matching verification template (Verification code vs Verification link) for both HTML and plaintext. 3. Ensure you have a custom email server configured and templates show up under courier.smtp.templates in ory get identity-config. 4. Verify that the template entries are base64://… (not `http://`/`file://`) in Ory Network. [[GitOps + templates](https://www.ory.com/docs/guides/gitops#email-templates); [[Network templates via base64](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)]boundless-ambulance-56979
12/01/2025, 6:26 PMchilly-king-10285
12/01/2025, 6:26 PMchilly-king-10285
12/01/2025, 6:26 PMcourier for templates to take effect.[\[Send emails](https://www.ory.com/docs/kratos/emails-sms/sending-emails-smtp)\] In Network this means your identity config (via ory get identity-config) must have something like: yaml courier: delivery_strategy: smtp # or http smtp: connection_uri: ... from_address: ... from_name: ... or an HTTP config. If you don’t see that, Ory is still using the default courier and will always send the built‑in templates.[\[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)\] 2. You edited the template that matches your verification method Which template is used depends on `selfservice.flows.verification.use`:[\[Email templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates)\] - If you use code: yaml selfservice: flows: verification: use: code then Ory uses: - verification_code.valid - verification_code.invalid - If you use link: yaml selfservice: flows: verification: use: link then Ory uses: - verification.valid - verification.invalid The text you see: > Your verification code is: … > Or click this link to verify: … is the default code template. So you must customize “Verification code / valid”, not just the link-based “Verification / valid” template.[\[Verification templates](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#email-templates)\] 3. Templates are stored as Base64 in the identity config On Ory Network, templates must be `base64://…`; file:// or http:// are not supported and will make Ory fall back to defaults.[\[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)\] After saving in the Console (Branding → Email templates), check with: bash ory get identity-config --project <project-id> --format yaml You should see something like: yaml courier: smtp: templates: verification_code: valid: email: body: html: "<base64://ENCODED_HTML>" plaintext: "<base64://ENCODED_TEXT>" If body is {} or missing, or uses unsupported URI schemes, Ory will send the default template instead.[\[Self-hosted vs Network templates](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#email-templates)\[\[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)\] --- What to do now 1. Verify in the Console (Authentication → Account verification) whether use is code or link. 2. In Branding → Email templates, edit the matching verification template: - Verification code / valid if use: code. 3. Confirm you have a custom SMTP/HTTP configured (Authentication → Email configuration) and that courier.smtp or courier.http exists in identity-config.[\[Send emails using your SMTP server](https://www.ory.com/docs/kratos/emails-sms/sending-emails-smtp#send-emails-using-your-smtp-server)\] 4. Check that the customized template appears under courier.smtp.templates.verification_code.valid (for use: code) with base64://… entries. If all of that is in place and you still see the default text, the knowledge sources don’t provide more detailed troubleshooting steps; in that case, sharing your ory get identity-config (redacted) with the Ory community/support would be the next step.