API Documentation

v1
Overview
The Golden Record API - Your single source of truth

The Golden Record API provides a centralized, authoritative source for managing organizations, users, roles, and permissions. It is designed for enterprise use cases where multiple applications need consistent access to organizational data.

Key Capabilities

  • Organization Management - CRUD operations for organizations with verified/staging status
  • User Management - User accounts with SSO integration via WorkOS
  • Role-Based Access Control - Flexible roles and permissions system
  • Organization Memberships - Connect users to organizations with specific roles
  • Secure API Keys - Scoped keys with rotation, expiration, and IP allowlisting
  • Real-time Sync - WorkOS User Management integration for SSO users
  • Fast Search - Typesense-powered autocomplete for organizations
  • Usage Analytics - Track API usage patterns and performance

Base URL

https://your-domain.com/api/v1

API Versioning

The API uses URL path versioning (e.g., /api/v1/). All endpoints are prefixed with the version number. When breaking changes are introduced, a new version will be released while maintaining backwards compatibility for existing versions.

Authentication
How to authenticate with the Golden Record API

All API requests require authentication using a Bearer token. Include your API key in the Authorization header:

Authorization: Bearer gr_live_your_api_key_here

API Key Types

gr_live_*

Production keys - Use these for live applications. All data operations affect real records.

gr_test_*

Test keys - For development and staging environments. (Coming soon)

Obtaining API Keys

  1. Log in to the Admin Dashboard with a @gloo.us email
  2. Navigate to API Keys in the sidebar
  3. Click Create API Key
  4. Select the required scopes and optional restrictions
  5. Copy and securely store the key - it will only be shown once

Security Note: Never expose your API keys in client-side code, public repositories, or logs. Use environment variables and server-side requests only.

Security Features
Enterprise-grade security for your API access

Scope-Based Access Control

API keys can be granted specific scopes that limit what operations they can perform. Always follow the principle of least privilege - only grant scopes that are necessary.

ScopeDescriptionEndpoints
organizations:readList and retrieve organization detailsGET /organizations, GET /organizations/:id
organizations:createCreate new organizationsPOST /organizations
organizations:updateUpdate existing organizations, toggle verificationPUT /organizations/:id, POST /organizations/:id/verify
organizations:deleteDelete organizationsDELETE /organizations/:id
users:readList and retrieve user details and membershipsGET /users, GET /users/:id, GET /memberships
users:createCreate users and organization membershipsPOST /users, POST /memberships
users:updateUpdate existing usersPUT /users/:id
users:deleteDelete usersDELETE /users/:id
roles:readList roles and their permissionsGET /roles
permissions:readList all available permissionsGET /permissions
api_keys:readList API keys and view analyticsGET /keys, GET /analytics
api_keys:createCreate and rotate API keysPOST /keys, POST /keys/:id?action=rotate
api_keys:revokeRevoke API keysDELETE /keys/:id
webhooks:readList and retrieve webhook subscriptionsGET /webhooks, GET /webhooks/:id
webhooks:writeCreate, update, and delete webhook subscriptionsPOST /webhooks, PUT /webhooks/:id, DELETE /webhooks/:id
*:*Full administrative access to all resourcesAll endpoints

Organization-Scoped Keys

API keys can be scoped to a specific organization, providing strict data isolation:

  • Org-scoped keys can only access data belonging to their organization
  • Attempting to access other organizations returns GR_ORG_SCOPE_VIOLATION
  • Global keys (no organization scope) can access all data based on their scopes
  • Organization scope is set at key creation time and cannot be modified

IP Allowlisting

Restrict API key usage to specific IP addresses or CIDR ranges for additional security:

{
  "name": "Production Key",
  "scopes": ["organizations:read"],
  "allowedIps": [
    "203.0.113.50",        // Single IP
    "192.168.1.0/24",      // CIDR range (256 IPs)
    "10.0.0.0/8"           // Large internal range
  ]
}
  • IPv4 addresses and CIDR notation supported (/0 to /32)
  • Requests from non-allowed IPs receive GR_IP_NOT_ALLOWED
  • Leave empty or omit to allow all IPs
  • IP restrictions are set at key creation and cannot be modified

Key Rotation

Rotate API keys with zero downtime using the 7-day grace period:

  1. Call POST /api/v1/keys/:id?action=rotate
  2. Receive new key immediately - start using it in your applications
  3. Old key continues working for 7 days
  4. Update all applications to use the new key
  5. Old key automatically expires after grace period

Key Expiration

Set expiration dates on API keys for temporary access:

{
  "name": "Temporary Integration Key",
  "scopes": ["organizations:read"],
  "expiresAt": "2024-12-31T23:59:59Z"
}

Expired keys return GR_INVALID_API_KEY. Keys without an expiration date never expire.

Rate Limits
API usage limits by plan tier
60
Free
requests/minute
120
Basic
requests/minute
300
Pro
requests/minute
1000
Enterprise
requests/minute

Rate Limit Headers

All responses include headers with rate limit information:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1706745600

Handling Rate Limits

When rate limited, you will receive a 429 response with GR_RATE_LIMITED:

{
  "success": false,
  "errors": [{
    "code": "GR_RATE_LIMITED",
    "message": "Rate limit exceeded. Try again in 45 seconds."
  }],
  "requestId": "req_abc123"
}

// Response Headers:
// Retry-After: 45
// X-RateLimit-Reset: 1706745645

Implement exponential backoff and respect the Retry-After header value.

Response Format
All responses follow a consistent envelope format

Success Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Corporation",
    "slug": "acme-corp",
    ...
  },
  "meta": {
    "limit": 20,
    "total": 150,
    "hasMore": true,
    "nextCursor": "eyJjcmVhdGVkQXQiOi4uLn0"
  },
  "requestId": "req_abc123def456"
}

Error Response

{
  "success": false,
  "data": null,
  "errors": [
    {
      "code": "GR_VALIDATION_ERROR",
      "message": "Validation failed",
      "field": "slug",
      "details": {
        "pattern": "Slug must contain only lowercase letters, numbers, and hyphens"
      }
    }
  ],
  "requestId": "req_abc123def456"
}

Response Fields

success - Boolean indicating if the request succeeded
data - The response payload (single object or array)
meta - Pagination info and other metadata
errors - Array of error objects (only on failure)
requestId - Unique identifier for debugging and support
Error Handling
Comprehensive error codes with resolution guidance
CodeStatusDescriptionResolution
GR_UNAUTHORIZED
401
Authentication required - no API key provided or invalid Authorization header formatInclude a valid Bearer token in the Authorization header
GR_INVALID_API_KEY
401
The API key is invalid, expired, or has been revokedCheck the key is correct and active. Generate a new key if needed
GR_FORBIDDEN
403
The API key lacks the required scope for this operationUse a key with the appropriate scopes or request additional permissions
GR_IP_NOT_ALLOWED
403
Request IP address is not in the key's allowed IP listAdd your IP to the key's allowedIps list or remove IP restrictions
GR_ORG_SCOPE_VIOLATION
403
Org-scoped key attempted to access a different organization's dataUse a global key or the key scoped to the target organization
GR_NOT_FOUND
404
The requested resource was not foundVerify the resource ID is correct
GR_ORG_NOT_FOUND
404
Organization with the specified ID does not existVerify the organization ID or check if it was deleted
GR_USER_NOT_FOUND
404
User with the specified ID does not existVerify the user ID or check if they were deleted
GR_KEY_NOT_FOUND
404
API key with the specified ID does not existVerify the key ID or check if it was revoked
GR_VALIDATION_ERROR
400
Request body or parameters failed validationCheck the error details for specific field issues
GR_DUPLICATE_SLUG
409
An organization with this slug already existsUse a different slug value
GR_DUPLICATE_EMAIL
409
A user with this email already existsUse a different email or update the existing user
GR_RATE_LIMITED
429
Too many requests - rate limit exceededWait until the rate limit resets (see Retry-After header)
GR_INTERNAL_ERROR
500
An unexpected server error occurredRetry the request. If persistent, contact support with the requestId
Organizations
Manage organizations in the Golden Record

Organizations are the primary entities in the Golden Record. They can be verified (visible in public API results) or staging (hidden by default, useful for testing or pending approval).

Organization Object

{
  "id": "550e8400-e29b-41d4-a716-446655440000",  // UUID
  "name": "Acme Corporation",                     // Display name
  "slug": "acme-corp",                           // URL-friendly identifier
  "domain": "acme.com",                          // Primary domain (optional)
  "logoUrl": "https://example.com/logo.png",     // Logo URL (optional)
  "workosOrgId": "org_01ABC123",                 // WorkOS organization ID (if synced)
  "isVerified": true,                            // Verified = public, false = staging
  "isActive": true,                              // Soft-delete flag
  "metadata": { "industry": "technology" },      // Custom data (optional)
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:30:00Z"
}

Endpoints

GET/api/v1/organizations
organizations:read

Retrieve a paginated list of organizations. By default, only verified organizations are returned.

Query Parameters

limit(integer)default: 20- Number of results per page (max 100)
cursor(string)- Cursor for pagination (from previous response)
search(string)- Search by name or slug
includeStaging(boolean)default: false- Include non-verified (staging) organizations

Response Example

{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Acme Corporation",
      "slug": "acme-corp",
      "domain": "acme.com",
      "logoUrl": "https://example.com/logo.png",
      "workosOrgId": "org_01ABC123",
      "isVerified": true,
      "isActive": true,
      "metadata": { "industry": "technology" },
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": {
    "limit": 20,
    "total": 150,
    "hasMore": true,
    "nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI0LTAxLTE1VDEwOjMwOjAwWiIsImlkIjoiNTUwZTg0MDAtZTI5Yi00MWQ0LWE3MTYtNDQ2NjU1NDQwMDAwIn0"
  },
  "requestId": "req_abc123def456"
}
POST/api/v1/organizations
organizations:create

Create a new organization. New organizations are created as staging (unverified) by default.

Request Body

name(string)
required
- Organization display name
slug(string)
required
- URL-friendly identifier (lowercase, alphanumeric, hyphens only)
domain(string)- Primary domain (e.g., acme.com)
logoUrl(string)- URL to organization logo
metadata(object)- Custom key-value data

Request Example

{
  "name": "Acme Corporation",
  "slug": "acme-corp",
  "domain": "acme.com",
  "metadata": {
    "industry": "technology",
    "size": "enterprise"
  }
}

Response Example

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Corporation",
    "slug": "acme-corp",
    "domain": "acme.com",
    "logoUrl": null,
    "workosOrgId": null,
    "isVerified": false,
    "isActive": true,
    "metadata": { "industry": "technology", "size": "enterprise" },
    "createdAt": "2024-01-15T10:30:00Z",
    "updatedAt": "2024-01-15T10:30:00Z"
  },
  "requestId": "req_abc123def456"
}
GET/api/v1/organizations/:id
organizations:read

Retrieve a single organization by ID. Org-scoped API keys can only access their own organization.

Query Parameters

id(uuid)
required
- Organization ID
PUT/api/v1/organizations/:id
organizations:update

Update an existing organization. Only provided fields are updated.

Request Body

name(string)- Organization display name
slug(string)- URL-friendly identifier
domain(string)- Primary domain
logoUrl(string)- URL to organization logo
isActive(boolean)- Whether the organization is active
metadata(object)- Custom key-value data (replaces existing)
DELETE/api/v1/organizations/:id
organizations:delete

Permanently delete an organization and all associated data including memberships.

POST/api/v1/organizations/:id/verify
organizations:update

Toggle organization verification status. Verified organizations appear in public API results.

Response Example

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "isVerified": true,
    "message": "Organization verified successfully"
  },
  "requestId": "req_abc123def456"
}
Users
Manage user accounts

Users represent individual accounts in the system. They can be synced from WorkOS (SSO users) or created directly via the API.

User Object

{
  "id": "user_01ABC123",                         // UUID
  "email": "john.doe@acme.com",                  // Email address (unique)
  "firstName": "John",                           // First name (optional)
  "lastName": "Doe",                             // Last name (optional)
  "avatarUrl": "https://example.com/avatar.jpg", // Avatar URL (optional)
  "workosUserId": "user_01WORKOS123",           // WorkOS user ID (if synced via SSO)
  "isActive": true,                              // Active status
  "metadata": { "department": "Engineering" },   // Custom data (optional)
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:30:00Z"
}

Endpoints

GET/api/v1/users
users:read

Retrieve a paginated list of users.

Query Parameters

limit(integer)default: 20- Number of results per page (max 100)
cursor(string)- Cursor for pagination
search(string)- Search by email, first name, or last name
organizationId(uuid)- Filter by organization membership

Response Example

{
  "success": true,
  "data": [
    {
      "id": "user_01ABC123",
      "email": "john.doe@acme.com",
      "firstName": "John",
      "lastName": "Doe",
      "avatarUrl": "https://example.com/avatar.jpg",
      "workosUserId": "user_01WORKOS123",
      "isActive": true,
      "metadata": { "department": "Engineering" },
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": {
    "limit": 20,
    "total": 50,
    "hasMore": false,
    "nextCursor": null
  },
  "requestId": "req_abc123def456"
}
POST/api/v1/users
users:create

Create a new user account.

Request Body

email(string)
required
- User email address (must be unique)
firstName(string)- First name
lastName(string)- Last name
avatarUrl(string)- URL to user avatar
metadata(object)- Custom key-value data
GET/api/v1/users/:id
users:read

Retrieve a single user by ID.

PUT/api/v1/users/:id
users:update

Update an existing user.

Request Body

email(string)- User email address
firstName(string)- First name
lastName(string)- Last name
avatarUrl(string)- URL to user avatar
isActive(boolean)- Whether the user is active
metadata(object)- Custom key-value data
DELETE/api/v1/users/:id
users:delete

Permanently delete a user and all associated memberships.

Memberships
Connect users to organizations with roles

Memberships define the relationship between users and organizations, including their assigned role.

Endpoints

GET/api/v1/memberships
users:read

List organization memberships. Returns users with their roles in organizations.

Query Parameters

organizationId(uuid)- Filter by organization
userId(uuid)- Filter by user
limit(integer)default: 20- Number of results per page
cursor(string)- Cursor for pagination

Response Example

{
  "success": true,
  "data": [
    {
      "id": "mem_01ABC123",
      "organizationId": "550e8400-e29b-41d4-a716-446655440000",
      "userId": "user_01ABC123",
      "roleId": "role_01ABC123",
      "isOwner": true,
      "createdAt": "2024-01-15T10:30:00Z",
      "user": {
        "id": "user_01ABC123",
        "email": "john.doe@acme.com",
        "firstName": "John",
        "lastName": "Doe"
      },
      "organization": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "Acme Corporation",
        "slug": "acme-corp"
      },
      "role": {
        "id": "role_01ABC123",
        "name": "Admin",
        "slug": "admin"
      }
    }
  ],
  "meta": { "limit": 20, "total": 5, "hasMore": false },
  "requestId": "req_abc123def456"
}
POST/api/v1/memberships
users:create

Add a user to an organization with a specific role.

Request Body

organizationId(uuid)
required
- Organization to add user to
userId(uuid)
required
- User to add
roleId(uuid)
required
- Role to assign
isOwner(boolean)default: false- Whether user is organization owner
Roles & Permissions
Role-based access control system

The RBAC system provides flexible permission management. Roles contain sets of permissions that define what actions users can perform.

Roles Endpoint

GET/api/v1/roles
roles:read

List all roles with their associated permissions.

Response Example

{
  "success": true,
  "data": [
    {
      "id": "role_01ABC123",
      "name": "Admin",
      "slug": "admin",
      "description": "Full administrative access",
      "isSystem": true,
      "permissions": [
        { "id": "perm_01", "name": "Read Organizations", "slug": "organizations:read", "resource": "organizations", "action": "read" },
        { "id": "perm_02", "name": "Create Organizations", "slug": "organizations:create", "resource": "organizations", "action": "create" }
      ],
      "createdAt": "2024-01-15T10:30:00Z"
    },
    {
      "id": "role_02ABC123",
      "name": "Member",
      "slug": "member",
      "description": "Basic member access",
      "isSystem": true,
      "permissions": [
        { "id": "perm_01", "name": "Read Organizations", "slug": "organizations:read", "resource": "organizations", "action": "read" }
      ],
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": { "total": 2 },
  "requestId": "req_abc123def456"
}

Permissions Endpoint

GET/api/v1/permissions
permissions:read

List all available permissions.

Response Example

{
  "success": true,
  "data": [
    { "id": "perm_01", "name": "Read Organizations", "slug": "organizations:read", "resource": "organizations", "action": "read" },
    { "id": "perm_02", "name": "Create Organizations", "slug": "organizations:create", "resource": "organizations", "action": "create" },
    { "id": "perm_03", "name": "Update Organizations", "slug": "organizations:update", "resource": "organizations", "action": "update" },
    { "id": "perm_04", "name": "Delete Organizations", "slug": "organizations:delete", "resource": "organizations", "action": "delete" },
    { "id": "perm_05", "name": "Read Users", "slug": "users:read", "resource": "users", "action": "read" },
    { "id": "perm_06", "name": "Create Users", "slug": "users:create", "resource": "users", "action": "create" },
    { "id": "perm_07", "name": "Update Users", "slug": "users:update", "resource": "users", "action": "update" },
    { "id": "perm_08", "name": "Delete Users", "slug": "users:delete", "resource": "users", "action": "delete" }
  ],
  "meta": { "total": 8 },
  "requestId": "req_abc123def456"
}
API Keys
Manage API key lifecycle

API keys provide secure, scoped access to the Golden Record API. Keys can be organization-scoped, IP-restricted, and time-limited.

Endpoints

GET/api/v1/keys
api_keys:read

List API keys. Only returns key metadata, not the actual key values.

Query Parameters

organizationId(uuid)- Filter by organization scope

Response Example

{
  "success": true,
  "data": [
    {
      "id": "key_01ABC123",
      "name": "Production API Key",
      "keyPrefix": "gr_live_abc123",
      "organizationId": null,
      "scopes": ["organizations:read", "users:read"],
      "tier": "pro",
      "allowedIps": ["192.168.1.0/24", "10.0.0.1"],
      "expiresAt": "2025-01-15T10:30:00Z",
      "lastUsedAt": "2024-01-20T15:45:00Z",
      "isActive": true,
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": { "total": 3 },
  "requestId": "req_abc123def456"
}
POST/api/v1/keys
api_keys:create

Create a new API key. The full key is only returned once at creation time - store it securely!

Request Body

name(string)
required
- Descriptive name for the key
scopes(array)
required
- Array of permission scopes
organizationId(uuid)- Scope key to specific organization
tier(string)default: free- Rate limit tier: free, basic, pro, enterprise
allowedIps(array)- IP addresses/CIDR ranges allowed to use this key
expiresAt(datetime)- Key expiration date (ISO 8601)

Request Example

{
  "name": "Production API Key",
  "scopes": ["organizations:read", "organizations:create", "users:read"],
  "tier": "pro",
  "allowedIps": ["192.168.1.0/24", "10.0.0.1"],
  "expiresAt": "2025-12-31T23:59:59Z"
}

Response Example

{
  "success": true,
  "data": {
    "id": "key_01ABC123",
    "name": "Production API Key",
    "key": "gr_live_sk_1234567890abcdefghijklmnopqrstuvwxyz",
    "keyPrefix": "gr_live_sk_12345",
    "organizationId": null,
    "scopes": ["organizations:read", "organizations:create", "users:read"],
    "tier": "pro",
    "allowedIps": ["192.168.1.0/24", "10.0.0.1"],
    "expiresAt": "2025-12-31T23:59:59Z",
    "isActive": true,
    "createdAt": "2024-01-15T10:30:00Z"
  },
  "requestId": "req_abc123def456"
}
POST/api/v1/keys/:id?action=rotate
api_keys:create

Rotate an API key. Creates a new key and schedules the old one to expire after a 7-day grace period for seamless migration.

Response Example

{
  "success": true,
  "data": {
    "oldKey": {
      "id": "key_01ABC123",
      "keyPrefix": "gr_live_sk_12345",
      "expiresAt": "2024-01-22T10:30:00Z",
      "message": "Old key will expire in 7 days"
    },
    "newKey": {
      "id": "key_02DEF456",
      "key": "gr_live_sk_newkey1234567890abcdefghij",
      "keyPrefix": "gr_live_sk_newke",
      "isActive": true,
      "createdAt": "2024-01-15T10:30:00Z"
    }
  },
  "requestId": "req_abc123def456"
}
DELETE/api/v1/keys/:id
api_keys:revoke

Immediately revoke an API key. The key will no longer work for authentication.

Analytics
Track API usage and performance

The analytics endpoint provides insights into API usage patterns, response times, and error rates.

GET/api/v1/analytics
api_keys:read

Retrieve API usage analytics for the authenticated key or organization.

Query Parameters

startDate(datetime)
required
- Start of date range (ISO 8601)
endDate(datetime)
required
- End of date range (ISO 8601)
endpoint(string)- Filter by specific endpoint
groupBy(string)- Group results: day, hour, endpoint

Response Example

{
  "success": true,
  "data": {
    "summary": {
      "totalRequests": 15420,
      "successfulRequests": 15100,
      "failedRequests": 320,
      "avgResponseTimeMs": 45,
      "successRate": 97.9
    },
    "byEndpoint": [
      { "endpoint": "/api/v1/organizations", "method": "GET", "count": 8500, "avgMs": 35 },
      { "endpoint": "/api/v1/users", "method": "GET", "count": 4200, "avgMs": 42 },
      { "endpoint": "/api/v1/organizations", "method": "POST", "count": 2720, "avgMs": 68 }
    ],
    "byDay": [
      { "date": "2024-01-15", "count": 2200 },
      { "date": "2024-01-16", "count": 2450 },
      { "date": "2024-01-17", "count": 2100 }
    ]
  },
  "meta": {
    "startDate": "2024-01-15T00:00:00Z",
    "endDate": "2024-01-22T00:00:00Z"
  },
  "requestId": "req_abc123def456"
}
Webhooks
Receive real-time notifications and manage webhook subscriptions

Webhooks allow your application to receive real-time HTTP notifications when events occur in the Golden Record. You can programmatically manage webhook subscriptions using the same API key that gives you access to other resources.

Managing Webhook Subscriptions

Use the Webhooks API to create, list, update, and delete webhook subscriptions. The same API key works for both managing webhooks and accessing other Golden Record resources.

GET/api/v1/webhooks
webhooks:read

List all webhook subscriptions for your account.

Query Parameters

limit(integer)default: 20- Number of results per page (max 100)
cursor(string)- Cursor for pagination

Response Example

{
  "success": true,
  "data": [
    {
      "id": "wh_01ABC123",
      "name": "Production Webhook",
      "url": "https://example.com/webhooks/golden-record",
      "events": ["organization.created", "organization.updated", "user.created"],
      "isActive": true,
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": { "total": 1 },
  "requestId": "req_abc123def456"
}
POST/api/v1/webhooks
webhooks:write

Create a new webhook subscription. The signing secret is only returned once at creation - store it securely!

Request Body

name(string)
required
- Descriptive name for the webhook
url(string)
required
- HTTPS endpoint URL to receive webhook events
events(array)
required
- Array of event types to subscribe to (use '*' for all events)
metadata(object)- Custom key-value data

Request Example

{
  "name": "Production Webhook",
  "url": "https://example.com/webhooks/golden-record",
  "events": ["organization.created", "organization.updated", "user.created"]
}

Response Example

{
  "success": true,
  "data": {
    "id": "wh_01ABC123",
    "name": "Production Webhook",
    "url": "https://example.com/webhooks/golden-record",
    "secret": "whsec_abc123def456ghijklmnopqrstuvwxyz1234567890",
    "events": ["organization.created", "organization.updated", "user.created"],
    "isActive": true,
    "createdAt": "2024-01-15T10:30:00Z"
  },
  "requestId": "req_abc123def456"
}
GET/api/v1/webhooks/:id
webhooks:read

Retrieve a single webhook subscription by ID.

Query Parameters

id(uuid)
required
- Webhook subscription ID
PUT/api/v1/webhooks/:id
webhooks:write

Update a webhook subscription. You can change the URL, events, or active status.

Request Body

name(string)- Updated name
url(string)- Updated endpoint URL
events(array)- Updated event types array
isActive(boolean)- Enable or disable the webhook

Request Example

{
  "events": ["*"],
  "isActive": true
}
DELETE/api/v1/webhooks/:id
webhooks:write

Delete a webhook subscription. The subscription will immediately stop receiving events.

Webhook Events

Subscribe to specific events or use * to receive all events. Events are delivered in real-time as changes occur in the system.

Organization Events

organization.created - New organization created
organization.updated - Organization details changed
organization.deleted - Organization deleted

User Events

user.created - New user created
user.updated - User details changed
user.deleted - User deleted

Membership Events

membership.created - User added to organization
membership.deleted - User removed from organization

Wildcard

* - Subscribe to all events

Event Payload Examples

organization.created

{
  "event": "organization.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Corporation",
    "slug": "acme-corp",
    "domain": "acme.com",
    "isVerified": false,
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

user.created

{
  "event": "user.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "id": "user_01ABC123",
    "email": "john.doe@acme.com",
    "firstName": "John",
    "lastName": "Doe",
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

membership.created

{
  "event": "membership.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "id": "mem_01ABC123",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "userId": "user_01ABC123",
    "roleId": "role_01ABC123",
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

Retry Behavior

If your endpoint fails to respond with a 2xx status code, we automatically retry delivery with exponential backoff:

Attempt 1
Immediate
Attempt 2
~1 min
Attempt 3
~2 min
Attempt 4
~4 min
Attempt 5
~8 min

After 5 failed attempts, the delivery is marked as failed.

Webhook Signature Verification

All webhook payloads are signed using HMAC-SHA256. Always verify signatures to ensure requests are authentic and prevent replay attacks.

Signature Headers

X-Webhook-Timestamp: 1706745600
X-Webhook-Signature: sha256=abc123def456...

Verification Algorithm

  1. Extract timestamp and signature from headers
  2. Reject if timestamp is older than 300 seconds (5 minutes) - prevents replay attacks
  3. Construct signed payload: {timestamp}.{raw_body}
  4. Compute HMAC-SHA256 of signed payload using your webhook secret
  5. Compare computed signature with received signature (use constant-time comparison)

Node.js Verification Example

import crypto from 'crypto';

function verifyWebhook(rawBody: string, headers: Headers, secret: string): boolean {
  const timestamp = headers.get('X-Webhook-Timestamp');
  const signature = headers.get('X-Webhook-Signature');
  
  if (!timestamp || !signature) {
    return false;
  }
  
  // Check timestamp is within 5 minutes
  const timestampAge = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (timestampAge > 300) {
    return false; // Replay attack protection
  }
  
  // Compute expected signature
  const signedPayload = `${timestamp}.${rawBody}`;
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  
  // Constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Python Verification Example

import hmac
import hashlib
import time

def verify_webhook(raw_body: str, headers: dict, secret: str) -> bool:
    timestamp = headers.get('X-Webhook-Timestamp')
    signature = headers.get('X-Webhook-Signature')
    
    if not timestamp or not signature:
        return False
    
    # Check timestamp is within 5 minutes
    timestamp_age = int(time.time()) - int(timestamp)
    if timestamp_age > 300:
        return False
    
    # Compute expected signature
    signed_payload = f"{timestamp}.{raw_body}"
    expected_sig = 'sha256=' + hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    # Constant-time comparison
    return hmac.compare_digest(signature, expected_sig)

Best Practices

Do

  • Always verify webhook signatures
  • Respond with 200 within 30 seconds
  • Process webhooks asynchronously
  • Handle duplicate deliveries (idempotency)
  • Store the signing secret securely

Don't

  • Expose your webhook secret in client-side code
  • Process webhooks synchronously (risk timeout)
  • Ignore the timestamp header (replay risk)
  • Skip signature verification
  • Return errors for already-processed events
WorkOS Integration
SSO user synchronization via WorkOS User Management

The Golden Record integrates with WorkOS User Management to sync users who authenticate via SSO. This provides a unified view of all users across your applications.

How It Works

  1. Users authenticate via WorkOS AuthKit (SSO)
  2. WorkOS sends real-time webhook events for user lifecycle changes
  3. Golden Record creates/updates local user records
  4. User matching: checks by workosUserId first, then falls back to email

Sync Capabilities

Real-time Sync

Webhook events automatically sync user changes as they happen. Endpoint: POST /api/webhooks/workos

Bulk Sync

Admin-only endpoint to sync all WorkOS users at once. Endpoint: POST /api/workos/sync-users

WorkOS User Fields Synced

workosUserId - WorkOS user ID
email - User email address
firstName - First name
lastName - Last name
avatarUrl - Profile picture URL

Note: This integration uses WorkOS User Management APIs (for SSO users), not Directory Sync APIs. It syncs users who have logged in via SSO authentication.

Code Examples
Complete examples in popular languages

List Organizations

curl -X GET "https://your-domain.com/api/v1/organizations?limit=20" \
  -H "Authorization: Bearer gr_live_your_api_key"

Create Organization

curl -X POST "https://your-domain.com/api/v1/organizations" \
  -H "Authorization: Bearer gr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corporation",
    "slug": "acme-corp",
    "domain": "acme.com",
    "metadata": {"industry": "technology"}
  }'

Update Organization

curl -X PUT "https://your-domain.com/api/v1/organizations/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer gr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp (Updated)"
  }'

Rotate API Key

curl -X POST "https://your-domain.com/api/v1/keys/key_01ABC123?action=rotate" \
  -H "Authorization: Bearer gr_live_your_api_key"
OpenAPI Specification
Machine-readable API specification

The complete OpenAPI 3.0 specification is available for download. Use it to generate client libraries, import into API testing tools, or integrate with your development workflow.