diff --git a/lib/workos/organization.rb b/lib/workos/organization.rb index 9bad7fc3..19c98a20 100644 --- a/lib/workos/organization.rb +++ b/lib/workos/organization.rb @@ -12,6 +12,7 @@ class Organization :domains, :stripe_customer_id, :name, + :external_id, :allow_profiles_outside_organization, :created_at, :updated_at, @@ -22,6 +23,7 @@ def initialize(json) @id = hash[:id] @name = hash[:name] + @external_id = hash[:external_id] @allow_profiles_outside_organization = hash[:allow_profiles_outside_organization] @domains = hash[:domains] @stripe_customer_id = hash[:stripe_customer_id] @@ -33,6 +35,7 @@ def to_json(*) { id: id, name: name, + external_id: external_id, allow_profiles_outside_organization: allow_profiles_outside_organization, domains: domains, stripe_customer_id: stripe_customer_id, diff --git a/lib/workos/organizations.rb b/lib/workos/organizations.rb index af3dd472..ff83ca3d 100644 --- a/lib/workos/organizations.rb +++ b/lib/workos/organizations.rb @@ -75,6 +75,7 @@ def get_organization(id:) # @option domain_data [String] domain The domain that belongs to the organization. # @option domain_data [String] state The state of the domain. "verified" or "pending" # @param [String] name A unique, descriptive name for the organization + # @param [String] external_id The organization's external ID. # @param [String] idempotency_key An idempotency key # @param [Boolean, nil] allow_profiles_outside_organization Whether Connections # within the Organization allow profiles that are outside of the Organization's configured User Email Domains. @@ -85,11 +86,13 @@ def create_organization( domain_data: nil, domains: nil, name:, + external_id: nil, allow_profiles_outside_organization: nil, idempotency_key: nil ) body = { name: name } body[:domain_data] = domain_data if domain_data + body[:external_id] = external_id if external_id if domains warn_deprecation '`domains` is deprecated. Use `domain_data` instead.' @@ -123,22 +126,26 @@ def create_organization( # @option domain_data [String] state The state of the domain. "verified" or "pending" # @param [String] stripe_customer_id The Stripe customer ID associated with this organization. # @param [String] name A unique, descriptive name for the organization + # @param [String] external_id The organization's external ID. # @param [Boolean, nil] allow_profiles_outside_organization Whether Connections # within the Organization allow profiles that are outside of the Organization's configured User Email Domains. # Deprecated: If you need to allow sign-ins from any email domain, contact suppport@workos.com. # @param [Array] domains List of domains that belong to the organization. # Deprecated: Use domain_data instead. + # rubocop:disable Metrics/ParameterLists def update_organization( organization:, stripe_customer_id: nil, domain_data: nil, domains: nil, name: nil, + external_id: :not_set, allow_profiles_outside_organization: nil ) body = { name: name } body[:domain_data] = domain_data if domain_data body[:stripe_customer_id] = stripe_customer_id if stripe_customer_id + body[:external_id] = external_id if external_id != :not_set if domains warn_deprecation '`domains` is deprecated. Use `domain_data` instead.' @@ -162,6 +169,7 @@ def update_organization( WorkOS::Organization.new(response.body) end + # rubocop:enable Metrics/ParameterLists # Delete an Organization # diff --git a/lib/workos/user_management.rb b/lib/workos/user_management.rb index 09ab6c63..9b30e6d2 100644 --- a/lib/workos/user_management.rb +++ b/lib/workos/user_management.rb @@ -182,6 +182,7 @@ def list_users(options = {}) # @param [String] first_name The user's first name. # @param [String] last_name The user's last name. # @param [Boolean] email_verified Whether the user's email address was previously verified. + # @param [String] external_id The user's external ID. # @param [String] password_hash The user's hashed password. # @option [String] password_hash_type The algorithm originally used to hash the password. # @@ -193,6 +194,7 @@ def create_user( first_name: nil, last_name: nil, email_verified: nil, + external_id: nil, password_hash: nil, password_hash_type: nil ) @@ -204,6 +206,7 @@ def create_user( first_name: first_name, last_name: last_name, email_verified: email_verified, + external_id: external_id, password_hash: password_hash, password_hash_type: password_hash_type, }.compact, @@ -231,14 +234,14 @@ def create_user( # @return [WorkOS::User] def update_user( id:, - email: nil, - first_name: nil, - last_name: nil, - email_verified: nil, - external_id: nil, - password: nil, - password_hash: nil, - password_hash_type: nil + email: :not_set, + first_name: :not_set, + last_name: :not_set, + email_verified: :not_set, + external_id: :not_set, + password: :not_set, + password_hash: :not_set, + password_hash_type: :not_set ) request = put_request( path: "/user_management/users/#{id}", @@ -251,7 +254,7 @@ def update_user( password: password, password_hash: password_hash, password_hash_type: password_hash_type, - }.compact, + }.reject { |_, v| v == :not_set }, auth: true, ) diff --git a/spec/lib/workos/organizations_spec.rb b/spec/lib/workos/organizations_spec.rb index 2188c9f9..91394493 100644 --- a/spec/lib/workos/organizations_spec.rb +++ b/spec/lib/workos/organizations_spec.rb @@ -33,6 +33,21 @@ end end + context 'with external_id' do + it 'creates an organization with external_id' do + VCR.use_cassette 'organization/create_with_external_id' do + organization = described_class.create_organization( + name: 'Test Organization with External ID', + external_id: 'ext_org_123', + ) + + expect(organization.id).to start_with('org_') + expect(organization.name).to eq('Test Organization with External ID') + expect(organization.external_id).to eq('ext_org_123') + end + end + end + context 'with domains' do it 'creates an organization and warns' do VCR.use_cassette 'organization/create_with_domains' do @@ -310,6 +325,43 @@ end end end + context 'with an external_id' do + it 'updates the organization' do + VCR.use_cassette 'organization/update_with_external_id' do + organization = described_class.update_organization( + organization: 'org_01K0SQV0S6EPWK2ZDEFD1CP1JC', + name: 'Test Organization', + external_id: 'ext_org_456', + ) + + expect(organization.id).to eq('org_01K0SQV0S6EPWK2ZDEFD1CP1JC') + expect(organization.name).to eq('Test Organization') + expect(organization.external_id).to eq('ext_org_456') + end + end + end + + context 'can set external_id to null explicitly' do + it 'includes external_id null in request body' do + original_method = described_class.method(:put_request) + allow(described_class).to receive(:put_request) do |kwargs| + original_method.call(**kwargs) + end + + VCR.use_cassette 'organization/update_with_external_id_null' do + described_class.update_organization( + organization: 'org_01K0SQV0S6EPWK2ZDEFD1CP1JC', + name: 'Test Organization', + external_id: nil, + ) + end + + # Verify the spy captured the right call + expect(described_class).to have_received(:put_request).with( + hash_including(body: hash_including(external_id: nil)), + ) + end + end end describe '.delete_organization' do diff --git a/spec/lib/workos/user_management_spec.rb b/spec/lib/workos/user_management_spec.rb index 8ba0f023..5b595563 100644 --- a/spec/lib/workos/user_management_spec.rb +++ b/spec/lib/workos/user_management_spec.rb @@ -358,6 +358,22 @@ ) end + it 'creates a user with external_id' do + VCR.use_cassette 'user_management/create_user_with_external_id' do + user = described_class.create_user( + email: 'external@example.com', + first_name: 'External', + last_name: 'User', + external_id: 'ext_user_123', + ) + + expect(user.first_name).to eq('External') + expect(user.last_name).to eq('User') + expect(user.email).to eq('external@example.com') + expect(user.external_id).to eq('ext_user_123') + end + end + context 'with an invalid payload' do it 'returns an error' do VCR.use_cassette 'user_management/create_user_invalid' do @@ -426,6 +442,25 @@ ) end + it 'can set external_id to null explicitly' do + original_method = described_class.method(:put_request) + allow(described_class).to receive(:put_request) do |kwargs| + original_method.call(**kwargs) + end + + VCR.use_cassette 'user_management/update_user_external_id_null' do + described_class.update_user( + id: 'user_01K0SR53HJ58M957MYAB6TDZ9X', + first_name: 'John', + external_id: nil, + ) + end + + expect(described_class).to have_received(:put_request).with( + hash_including(body: hash_including(external_id: nil)), + ) + end + context 'with an invalid payload' do it 'returns an error' do VCR.use_cassette 'user_management/update_user/invalid' do diff --git a/spec/support/fixtures/vcr_cassettes/organization/create_with_external_id.yml b/spec/support/fixtures/vcr_cassettes/organization/create_with_external_id.yml new file mode 100644 index 00000000..92470cc5 --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/organization/create_with_external_id.yml @@ -0,0 +1,83 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.workos.com/organizations + body: + encoding: UTF-8 + string: '{"name":"Test Organization with External ID","external_id":"ext_org_123"}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - WorkOS; ruby/3.1.4; arm64-darwin24; v5.20.0 + Authorization: + - Bearer + response: + status: + code: 201 + message: Created + headers: + Date: + - Tue, 22 Jul 2025 18:55:20 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '286' + Connection: + - keep-alive + Cf-Ray: + - 963526d76e29aafd-YYZ + Cf-Cache-Status: + - DYNAMIC + Etag: + - W/"11e-Op9TWXVRjSmUm44yQl7F4OmXvVQ" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin, Accept-Encoding + Access-Control-Allow-Credentials: + - 'true' + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Expect-Ct: + - max-age=0 + Referrer-Policy: + - no-referrer + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - 'off' + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 56c9630e-4889-4c81-989b-5a0399cdfcc2 + X-Xss-Protection: + - '0' + Report-To: + - '{"endpoints":[{"url":"https:\/\/csp-reporting.cloudflare.com\/cdn-cgi\/script_monitor\/report?m=RvRh6EQA2egPx6JdFhsw.m.1RATK00LOdPev9hq3f8A-1753210520-1.0.1.1-Uchst_eeyl0_BAQRRODtUIhYDUBNZKYADoELB62ZrhleV2Q.J_Wdk59lUFdfwSqAAX2vWrW_nejregFofyr3sox0aNmZolb8G_7Nzpy2RD9uyKH4l3OgsDbo.LRttnqfDPXocdCCB9G61E4QJ8Q1ug"}],"group":"cf-csp-endpoint","max_age":86400}' + Content-Security-Policy-Report-Only: + - script-src 'none'; connect-src 'none'; report-uri https://csp-reporting.cloudflare.com/cdn-cgi/script_monitor/report?m=RvRh6EQA2egPx6JdFhsw.m.1RATK00LOdPev9hq3f8A-1753210520-1.0.1.1-Uchst_eeyl0_BAQRRODtUIhYDUBNZKYADoELB62ZrhleV2Q.J_Wdk59lUFdfwSqAAX2vWrW_nejregFofyr3sox0aNmZolb8G_7Nzpy2RD9uyKH4l3OgsDbo.LRttnqfDPXocdCCB9G61E4QJ8Q1ug; + report-to cf-csp-endpoint + Set-Cookie: + - _cfuvid=PQ8Lx7_xKyiAL1Mx.Ib3Gjf0xXL4n9.aJfbpov473xY-1753210520423-0.0.1.1-604800000; + path=/; domain=.workos.com; HttpOnly; Secure; SameSite=None + Server: + - cloudflare + body: + encoding: UTF-8 + string: '{"object":"organization","id":"org_01K0SQV0S6EPWK2ZDEFD1CP1JC","name":"Test + Organization with External ID","allow_profiles_outside_organization":false,"created_at":"2025-07-22T18:55:20.355Z","updated_at":"2025-07-22T18:55:20.355Z","domains":[],"metadata":{},"external_id":"ext_org_123"}' + http_version: + recorded_at: Tue, 22 Jul 2025 18:55:20 GMT +recorded_with: VCR 5.0.0 diff --git a/spec/support/fixtures/vcr_cassettes/organization/update_with_external_id.yml b/spec/support/fixtures/vcr_cassettes/organization/update_with_external_id.yml new file mode 100644 index 00000000..8811e883 --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/organization/update_with_external_id.yml @@ -0,0 +1,78 @@ +--- +http_interactions: +- request: + method: put + uri: https://api.workos.com/organizations/org_01K0SQV0S6EPWK2ZDEFD1CP1JC + body: + encoding: UTF-8 + string: '{"name":"Test Organization","external_id":"ext_org_456"}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - WorkOS; ruby/3.1.4; arm64-darwin24; v5.20.0 + Authorization: + - Bearer + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 22 Jul 2025 18:59:20 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Cf-Ray: + - 96352cb1598936bf-YYZ + Cf-Cache-Status: + - DYNAMIC + Etag: + - W/"10d-7PesLGj94PWb6A5HO530ZxGdEf4" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin, Accept-Encoding + Access-Control-Allow-Credentials: + - 'true' + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Expect-Ct: + - max-age=0 + Referrer-Policy: + - no-referrer + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - 'off' + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - fdca4330-1a27-4bd5-9e78-75e58eefb233 + X-Xss-Protection: + - '0' + Set-Cookie: + - _cfuvid=hbA98zPccWnkbrQxoYNYNSHeQq3brYDB.grPijC_WV4-1753210760158-0.0.1.1-604800000; + path=/; domain=.workos.com; HttpOnly; Secure; SameSite=None + Server: + - cloudflare + body: + encoding: ASCII-8BIT + string: '{"object":"organization","id":"org_01K0SQV0S6EPWK2ZDEFD1CP1JC","name":"Test + Organization","allow_profiles_outside_organization":false,"created_at":"2025-07-22T18:55:20.355Z","updated_at":"2025-07-22T18:59:20.064Z","domains":[],"metadata":{},"external_id":"ext_org_456"}' + http_version: + recorded_at: Tue, 22 Jul 2025 18:59:20 GMT +recorded_with: VCR 5.0.0 diff --git a/spec/support/fixtures/vcr_cassettes/organization/update_with_external_id_null.yml b/spec/support/fixtures/vcr_cassettes/organization/update_with_external_id_null.yml new file mode 100644 index 00000000..f01e5b11 --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/organization/update_with_external_id_null.yml @@ -0,0 +1,78 @@ +--- +http_interactions: +- request: + method: put + uri: https://api.workos.com/organizations/org_01K0SQV0S6EPWK2ZDEFD1CP1JC + body: + encoding: UTF-8 + string: '{"name":"Test Organization","external_id":null}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - WorkOS; ruby/3.1.4; arm64-darwin24; v5.22.0 + Authorization: + - Bearer + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 23 Jul 2025 14:19:40 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Cf-Ray: + - 963bd06b7d9fac70-YYZ + Cf-Cache-Status: + - DYNAMIC + Etag: + - W/"104-iVnG8ziU2vR/dhIQFse9HLEGA6c" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin, Accept-Encoding + Access-Control-Allow-Credentials: + - 'true' + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Expect-Ct: + - max-age=0 + Referrer-Policy: + - no-referrer + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - 'off' + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - f38969a5-158e-4ed5-b165-a7789d1b0a07 + X-Xss-Protection: + - '0' + Set-Cookie: + - _cfuvid=7pLWC5qh1CKmolFiECCkKsRg3QAx7aM07F6bX4r6VMU-1753280380885-0.0.1.1-604800000; + path=/; domain=.workos.com; HttpOnly; Secure; SameSite=None + Server: + - cloudflare + body: + encoding: ASCII-8BIT + string: '{"object":"organization","id":"org_01K0SQV0S6EPWK2ZDEFD1CP1JC","name":"Test + Organization","allow_profiles_outside_organization":false,"created_at":"2025-07-22T18:55:20.355Z","updated_at":"2025-07-23T14:19:40.831Z","domains":[],"metadata":{},"external_id":null}' + http_version: + recorded_at: Wed, 23 Jul 2025 14:19:40 GMT +recorded_with: VCR 5.0.0 diff --git a/spec/support/fixtures/vcr_cassettes/user_management/create_user_with_external_id.yml b/spec/support/fixtures/vcr_cassettes/user_management/create_user_with_external_id.yml new file mode 100644 index 00000000..2447ebdc --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/user_management/create_user_with_external_id.yml @@ -0,0 +1,77 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.workos.com/user_management/users + body: + encoding: UTF-8 + string: '{"email":"external@example.com","password":null,"first_name":"External","last_name":"User","email_verified":null,"external_id":"ext_user_123","password_hash":null,"password_hash_type":null}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - WorkOS; ruby/3.1.4; arm64-darwin24; v5.20.0 + Authorization: + - Bearer + response: + status: + code: 201 + message: Created + headers: + Date: + - Tue, 22 Jul 2025 19:00:50 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '326' + Connection: + - keep-alive + Cf-Ray: + - 96352ee9395c36b3-YYZ + Cf-Cache-Status: + - DYNAMIC + Etag: + - W/"146-upR+rp+FopOrmNrHPnshQZCSTFg" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin, Accept-Encoding + Access-Control-Allow-Credentials: + - 'true' + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Expect-Ct: + - max-age=0 + Referrer-Policy: + - no-referrer + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - 'off' + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 0ca01f80-ab79-4dac-ac38-85013ee190fc + X-Xss-Protection: + - '0' + Set-Cookie: + - _cfuvid=eS3jraDP_ZTVdNpNKtpQG80hPBRXhXcHuq1V_QbAQjY-1753210850912-0.0.1.1-604800000; + path=/; domain=.workos.com; HttpOnly; Secure; SameSite=None + Server: + - cloudflare + body: + encoding: UTF-8 + string: '{"object":"user","id":"user_01K0SR53HJ58M957MYAB6TDZ9X","email":"external@example.com","email_verified":false,"first_name":"External","last_name":"User","profile_picture_url":null,"metadata":{},"last_sign_in_at":null,"created_at":"2025-07-22T19:00:50.852Z","updated_at":"2025-07-22T19:00:50.852Z","external_id":"ext_user_123"}' + http_version: + recorded_at: Tue, 22 Jul 2025 19:00:50 GMT +recorded_with: VCR 5.0.0 diff --git a/spec/support/fixtures/vcr_cassettes/user_management/update_user_external_id_null.yml b/spec/support/fixtures/vcr_cassettes/user_management/update_user_external_id_null.yml new file mode 100644 index 00000000..fa81b88f --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/user_management/update_user_external_id_null.yml @@ -0,0 +1,77 @@ +--- +http_interactions: +- request: + method: put + uri: https://api.workos.com/user_management/users/user_01K0SR53HJ58M957MYAB6TDZ9X + body: + encoding: UTF-8 + string: '{"first_name":"John","external_id":null}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - WorkOS; ruby/3.1.4; arm64-darwin24; v5.22.0 + Authorization: + - Bearer + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 23 Jul 2025 14:19:37 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Cf-Ray: + - 963bd0578b723987-YYZ + Cf-Cache-Status: + - DYNAMIC + Etag: + - W/"138-cAQWhb1gyLa/WXSej+rjaxcQD5k" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin, Accept-Encoding + Access-Control-Allow-Credentials: + - 'true' + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Expect-Ct: + - max-age=0 + Referrer-Policy: + - no-referrer + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - 'off' + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - e32c5c22-9dba-480d-9b70-cb985f8de386 + X-Xss-Protection: + - '0' + Set-Cookie: + - _cfuvid=0ljO.TFpHbzOeVWd7XzlanO5UxaeU_RBUAsoWNtWaF0-1753280377738-0.0.1.1-604800000; + path=/; domain=.workos.com; HttpOnly; Secure; SameSite=None + Server: + - cloudflare + body: + encoding: ASCII-8BIT + string: '{"object":"user","id":"user_01K0SR53HJ58M957MYAB6TDZ9X","email":"external@example.com","email_verified":false,"first_name":"John","last_name":"User","profile_picture_url":null,"metadata":{},"last_sign_in_at":null,"created_at":"2025-07-22T19:00:50.852Z","updated_at":"2025-07-23T14:19:37.660Z","external_id":null}' + http_version: + recorded_at: Wed, 23 Jul 2025 14:19:37 GMT +recorded_with: VCR 5.0.0