The WorkOS Ruby SDK provides convenient access to the WorkOS API from applications written in Ruby.
Install the package with:
gem install workos
If you're using Bundler to manage your application's gems, add the WorkOS gem to your Gemfile:
source "https://rubygems.org" gem "workos"
Store your WorkOS API key (and, for AuthKit and SSO flows, your client ID) in environment variables such as WORKOS_API_KEY and WORKOS_CLIENT_ID, then pass them to the SDK explicitly. The SDK does not read these environment variables automatically — configure it in an initializer:
# /config/initializers/workos.rb require "workos" WorkOS.configure do |config| config.api_key = ENV.fetch("WORKOS_API_KEY") config.client_id = ENV["WORKOS_CLIENT_ID"] config.timeout = 120 config.logger = Logger.new($stdout) config.log_level = :info end client = WorkOS.client
WorkOS.configure do |config| config.api_key = ENV.fetch("WORKOS_API_KEY") config.client_id = ENV["WORKOS_CLIENT_ID"] end WorkOS.client.organizations.list_organizations
tenant_a = WorkOS::Client.new(api_key: "sk_tenant_a", client_id: "client_a") tenant_b = WorkOS::Client.new(api_key: "sk_tenant_b", client_id: "client_b") tenant_a.organizations.list_organizations tenant_b.organizations.list_organizations
public_client = WorkOS::PublicClient.create(client_id: "client_123") url, verifier, state = public_client.user_management.get_authorization_url_with_pkce( provider: "authkit", redirect_uri: "https://example.com/callback" )
The SDK caches persistent connections per fiber. After forking, call
WorkOS.reset_client (or client.shutdown) to close inherited sockets:
# config/puma.rb on_worker_boot { WorkOS.reset_client }
Every method that makes an HTTP request accepts request_options: for per-call overrides (local URL-building helpers such as get_authorization_url and get_logout_url do not):
organization = WorkOS.client.organizations.get_organization( id: "org_123", request_options: { timeout: 10, extra_headers: {"X-Request-Source" => "admin"} } )
Idempotency-Key is only sent on POST, PUT, and PATCH requests, and never on GET or DELETE. Every POST carries one: the key you provide in request_options[:idempotency_key], or one the SDK generates before the first attempt and reuses on every retry. PUT and PATCH requests carry one when you provide it, or when the SDK retries them after a transient failure.
Note
The WorkOS API currently honors Idempotency-Key only on the Create Audit Log Event endpoint (audit_logs.create_event) and on Pipes explicit adds (pipes.create_data_integration_api_key, pipes.create_data_integration_client_credential, and pipes.create_user_connected_account / pipes.create_organization_connected_account with connection_intent: "add"). Other endpoints accept the header but do not deduplicate requests, so a retried mutation elsewhere can still create a duplicate.
organizations = WorkOS.client.organizations.list_organizations(limit: 10) organizations.data.each do |organization| puts "#{organization.id}: #{organization.name}" end
organization = WorkOS.client.organizations.get_organization(id: "org_123") puts organization.name
user = WorkOS.client.user_management.create_user( email: "[email protected]", first_name: "Marceline", last_name: "Abadeer" ) puts user.id
When you use client.session_manager to seal session cookies, the
cookie_password you supply must be at least 32 bytes of high-entropy
secret material (typically 32 random bytes encoded as base64 or a 64-char
hex string). The SDK derives the AES-256-GCM key from this password via
SHA-256, and a passphrase shorter than 32 bytes makes the resulting key
materially easier to brute-force offline.
Generate a suitable secret once and store it as an environment variable:
ruby -rsecurerandom -e 'puts SecureRandom.base64(32)'Anything shorter than 32 bytes (including nil or "") raises
ArgumentError as soon as you load, seal, or unseal a session — sealing or
unsealing will not silently proceed with a weakened key.
By default the SDK verifies the signature and expiry of session access tokens
but does not check the iss claim. To also require a specific issuer (or one
of several), set jwt_issuer on the client — either a single string or an
array of accepted issuers:
WorkOS.configure do |config| config.jwt_issuer = "https://api.workos.com/user_management/#{ENV["WORKOS_CLIENT_ID"]}" end # or per client client = WorkOS::Client.new( api_key: ENV.fetch("WORKOS_API_KEY"), client_id: ENV["WORKOS_CLIENT_ID"], jwt_issuer: ["https://api.workos.com", "https://auth.example.com"] )
Tokens whose iss is not in the configured set fail authentication with
reason WorkOS::SessionManager::INVALID_JWT.
payload = request.body.read signature = request.env.fetch("HTTP_WORKOS_SIGNATURE") secret = ENV.fetch("WORKOS_WEBHOOK_SECRET") event = WorkOS.client.webhooks.construct_event( payload: payload, sig_header: signature, secret: secret ) puts event.event
List endpoints return WorkOS::Types::ListStruct, which supports inspecting pagination metadata or iterating through every record automatically.
users = WorkOS.client.user_management.list_users(limit: 100) users.auto_paging_each do |user| puts user.email end
You can also iterate page by page:
users.each_page do |page| puts page.list_metadata end
The SDK raises typed errors for API failures (WorkOS::APIError and
subclasses) and transport failures (WorkOS::APIConnectionError). Both
inherit from WorkOS::Error.
begin WorkOS.client.organizations.get_organization(id: "org_123") rescue WorkOS::APIError => e warn "#{e.class}: #{e.message}" warn "status=#{e.http_status} request_id=#{e.request_id} code=#{e.code}" rescue WorkOS::APIConnectionError => e warn "connection failure: #{e.message}" end
For our SDKs WorkOS follows a Semantic Versioning (SemVer) process where all releases will have a version X.Y.Z (like 1.0.0) pattern wherein Z would be a bug fix (e.g., 1.0.1), Y would be a minor release (1.1.0) and X would be a major release (2.0.0). We permit any breaking changes to only be released in major versions and strongly recommend reading changelogs before making any major version upgrades.
WorkOS has features in Beta that can be accessed via Beta releases. We would love for you to try these and share feedback with us before these features reach general availability (GA). To install a Beta version, please follow the installation steps above using the Beta release version.
Note: there can be breaking changes between Beta versions. Therefore, we recommend pinning the package version to a specific version. This way you can install the same version each time without breaking changes unless you are intentionally looking for the latest Beta version.
We highly recommend keeping an eye on when the Beta feature you are interested in goes from Beta to stable so that you can move to using the stable version.