WorkOS Domain Verification
Step 1: Fetch Documentation (BLOCKING)
STOP. Do not proceed until complete.
WebFetch these URLs for source of truth:
If this skill conflicts with the documentation, follow the documentation.
Step 2: Pre-Flight Validation
Environment Variables
Check for required WorkOS credentials:
-
WORKOS_API_KEY
-
starts with sk_
-
WORKOS_CLIENT_ID
-
starts with client_
Verify: Both variables are set in .env or .env.local
SDK Installation
Confirm WorkOS SDK is installed:
Check package.json contains @workos-inc/node
grep "@workos-inc/node" package.json || npm install @workos-inc/node
CRITICAL: SDK must be installed before writing any import statements.
Step 3: Organization Exists (REQUIRED)
All domains must belong to an Organization. You cannot create or verify a domain without an Organization.
Check if organization already exists:
If you have organization ID
curl https://api.workos.com/organizations/org_123
-H "Authorization: Bearer $WORKOS_API_KEY"
If no organization exists, create one first using the Organizations API (see workos-organizations skill).
Do NOT proceed to Step 4 until you have a valid Organization ID.
Step 4: Implementation Path (Decision Tree)
Choose based on your use case:
Domain verification flow? | +-- Self-serve (IT admin verifies) --> Admin Portal flow (Step 5A) | +-- Programmatic (you verify) --> API flow (Step 5B)
Self-serve = Your customer's IT admin proves ownership via DNS TXT records through the Admin Portal UI
Programmatic = You integrate domain creation/verification directly into your application code
Step 5A: Admin Portal Flow (Self-Serve)
Create Portal Link
Generate a temporary Admin Portal session link (valid 5 minutes):
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS(process.env.WORKOS_API_KEY);
const portalLink = await workos.portal.generateLink({ organization: 'org_123', intent: 'domain_verification', return_url: 'https://yourapp.com/settings/domains' });
// portalLink.link - redirect user here
Check documentation for exact method signature - may be createLink or generateLink depending on SDK version.
User Flow
-
Redirect user to portalLink.link
-
User adds domain in Admin Portal
-
User adds DNS TXT record to their domain's DNS
-
WorkOS verifies TXT record automatically
-
User returns to return_url when complete
Verification
Listen for webhook to know when domain is verified, or poll the domain status:
Get domain status
curl https://api.workos.com/organization_domains/org_domain_123
-H "Authorization: Bearer $WORKOS_API_KEY"
Check documentation for webhook event name - likely domain.verified or similar.
Step 5B: API Flow (Programmatic)
Create Domain
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS(process.env.WORKOS_API_KEY);
const domain = await workos.organizationDomains.create({ organization_id: 'org_123', domain: 'example.com' });
// domain.id - save this // domain.verification_token - share this with domain owner
Check documentation for exact method path - may be workos.domains.create() or similar.
Share Verification Instructions
The domain owner must add this DNS TXT record:
Type: TXT Host: _workos-challenge (or @ for root) Value: [domain.verification_token from response]
Check documentation for exact TXT record format - host name may vary.
Trigger Verification
After DNS TXT record is added, trigger verification check:
await workos.organizationDomains.verify({ domain_id: domain.id });
Check documentation for exact method name - may be verify() , checkVerification() , or automatic.
Poll for Status
Check if verification succeeded:
const updatedDomain = await workos.organizationDomains.get(domain.id);
if (updatedDomain.state === 'verified') { // Domain verified successfully } else if (updatedDomain.state === 'pending') { // Still waiting for DNS propagation } else if (updatedDomain.state === 'failed') { // Verification failed - check TXT record }
Check documentation for exact state values - may be status instead of state , may use different enum values.
Step 6: List Domains for Organization
Retrieve all domains (verified and unverified) for an organization:
const domains = await workos.organizationDomains.list({ organization_id: 'org_123' });
domains.data.forEach(domain => {
console.log(${domain.domain}: ${domain.state});
});
Verification Checklist (ALL MUST PASS)
Run these commands to confirm integration:
1. Check environment variables exist
env | grep WORKOS_API_KEY && env | grep WORKOS_CLIENT_ID
2. Check SDK is installed
npm list @workos-inc/node 2>/dev/null || echo "FAIL: SDK not installed"
3. Verify organization exists (replace org_123 with your ID)
curl -s https://api.workos.com/organizations/org_123
-H "Authorization: Bearer $WORKOS_API_KEY" | grep '"id"'
4. Test domain creation (replace with test domain)
curl -X POST https://api.workos.com/organization_domains
-H "Authorization: Bearer $WORKOS_API_KEY"
-H "Content-Type: application/json"
-d '{"organization_id":"org_123","domain":"test.example.com"}'
5. Build succeeds
npm run build
If check #3 fails: Create organization first (see Step 3) If check #4 fails: Check API key permissions or organization ID
Error Recovery
"Organization not found"
Root cause: No organization exists or wrong ID
Fix:
-
List organizations: curl https://api.workos.com/organizations -H "Authorization: Bearer $WORKOS_API_KEY"
-
If empty, create organization first
-
Use correct organization ID in domain creation
"Invalid domain format"
Root cause: Domain contains invalid characters or format
Fix:
-
Use bare domain: example.com not https://example.com
-
No subdomains for verification (unless docs explicitly allow)
-
Check documentation for allowed domain formats
"Domain already exists"
Root cause: Domain already registered to this or another organization
Fix:
-
List domains: workos.organizationDomains.list()
-
If domain belongs to current org, use existing domain ID
-
If domain belongs to different org, cannot claim (security feature)
"Verification failed" / "TXT record not found"
Root cause: DNS TXT record not added correctly or DNS not propagated
Fix:
-
Check TXT record exists: dig TXT _workos-challenge.example.com or nslookup -type=TXT _workos-challenge.example.com
-
Verify token matches exactly (no extra quotes or spaces)
-
Wait for DNS propagation (can take up to 48 hours, usually <15 minutes)
-
Check documentation for exact TXT record host format
"API key does not have permission"
Root cause: API key lacks domain verification scope
Fix:
-
Go to WorkOS Dashboard → API Keys
-
Check key has required permissions
-
Generate new key if needed
SDK method not found
Root cause: SDK version mismatch with documentation
Fix:
-
Check SDK version: npm list @workos-inc/node
-
Check documentation for method names matching your SDK version
-
Update SDK: npm install @workos-inc/node@latest
Portal link expired
Root cause: Portal links are valid for 5 minutes only
Fix:
-
Generate new portal link immediately before redirecting user
-
Do not cache or reuse portal links
Related Skills
-
workos-organizations: Creating organizations (required before domain verification)
-
workos-sso: SSO connections require verified domains
-
workos-directory-sync: Directory Sync requires verified domains
-
workos-admin-portal: General Admin Portal integration patterns