Skip to content

Authentication & Security

GrowthOS uses two distinct API key types, each designed for a specific trust boundary.

Write Key

Prefix: gos_wk_

Client-safe. Embed directly in your JavaScript SDK, browser code, or mobile app. Write keys can only call Ingest API endpoints — they cannot read data, modify resources, or access admin functions.

Auto-generated when you create a project. One write key per project by default.

Secret Key

Prefix: gos_sk_

Server-side only. Never expose in client-side code, public repos, or frontend bundles. Secret keys call the Management API and are created with granular permission scopes.

You can create multiple secret keys per project, each with different scopes for different services.


The Ingest API accepts write keys via header or query parameter.

Option 1 — Authorization header (recommended):

POST /v1/track HTTP/1.1
Host: ingest.growthos.io
Authorization: Bearer gos_wk_your_write_key
Content-Type: application/json
{
"event": "Signup Completed",
"user_id": "user_123",
"properties": {
"plan": "growth"
}
}

Option 2 — Query parameter:

POST /v1/track?writeKey=gos_wk_your_write_key

Secret keys are created with granular scopes that control which resources and operations the key can access. Follow the principle of least privilege — grant only the scopes each service needs.

ScopePermission
contacts:readView contacts and their properties
contacts:writeCreate, update, and delete contacts
events:readQuery event history and raw event data
campaigns:readView email campaigns and their status
campaigns:writeCreate, update, send, and archive campaigns
referrals:readView referral programs and participant data
referrals:writeCreate and modify referral programs
segments:readView segment definitions and membership
segments:writeCreate, update, and delete segments
surveys:readView surveys and response data
surveys:writeCreate, update, and publish surveys
waitlists:readView waitlists and subscriber data
waitlists:writeCreate, manage, and promote waitlist entries
webhooks:readView webhook subscriptions
webhooks:writeCreate, update, and delete webhook subscriptions
analytics:readQuery analytics dashboards and reports
settings:readView project settings and configuration
settings:writeModify project settings
billing:readView billing information and usage
*Full access (admin) — grants all scopes
POST /v1/api-keys
{
"name": "Email Service",
"scopes": ["contacts:read", "campaigns:read", "campaigns:write"]
}

Response:

{
"id": "key_8x2m4k",
"name": "Email Service",
"key": "gos_sk_live_abc123...",
"scopes": ["contacts:read", "campaigns:read", "campaigns:write"],
"created_at": "2026-02-23T10:00:00Z"
}

If a key attempts an operation outside its scopes, the API returns 403 Forbidden:

{
"error": {
"code": "insufficient_scope",
"message": "This API key does not have the 'contacts:write' scope required for this operation.",
"details": [
{
"required_scope": "contacts:write",
"granted_scopes": ["contacts:read", "campaigns:read", "campaigns:write"]
}
]
},
"request_id": "req_9z3n5p7q"
}

GrowthOS enforces strict tenant isolation at the database level using PostgreSQL Row-Level Security (RLS).

  1. Every API request includes an API key (write key or secret key)
  2. The API gateway resolves the key to a tenant_id
  3. The database session variable app.current_tenant is set before any query executes
  4. RLS policies on every table enforce WHERE tenant_id = current_setting('app.current_tenant') automatically
  5. Results are guaranteed to contain only the requesting tenant’s data

No cross-tenant leaks

Even a SQL injection vulnerability cannot access another tenant’s data — the RLS policy is enforced by PostgreSQL itself, not application code.

Audit trail

Every API request is logged with tenant_id, api_key_id, and request_id for full traceability.


GrowthOS signs all outbound webhook payloads with HMAC-SHA256 so you can verify they originated from GrowthOS and have not been tampered with.

HeaderDescription
X-GrowthOS-SignatureHMAC-SHA256 hex digest of the signed payload
X-GrowthOS-TimestampUnix timestamp (seconds) when the webhook was sent
  1. Extract the X-GrowthOS-Timestamp and X-GrowthOS-Signature headers
  2. Concatenate: timestamp + "." + raw_request_body
  3. Compute HMAC-SHA256 using your webhook endpoint’s signing secret
  4. Compare the computed signature with X-GrowthOS-Signature using a timing-safe comparison
  5. Reject if the timestamp is older than 5 minutes (replay protection)
import crypto from 'crypto';
function verifyWebhook(req, signingSecret) {
const signature = req.headers['x-growthos-signature'];
const timestamp = req.headers['x-growthos-timestamp'];
const body = req.rawBody; // raw request body as string
// Replay protection: reject if older than 5 minutes
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (age > 300) {
throw new Error('Webhook timestamp too old — possible replay attack');
}
// Compute expected signature
const payload = `${timestamp}.${body}`;
const expected = crypto
.createHmac('sha256', signingSecret)
.update(payload)
.digest('hex');
// Timing-safe comparison
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
if (!isValid) {
throw new Error('Invalid webhook signature');
}
return JSON.parse(body);
}

Enterprise

For organizations with strict network security requirements, Management API access can be restricted to specific IP addresses or CIDR ranges.

POST /v1/settings/ip-allowlist
{
"ranges": [
"203.0.113.0/24",
"198.51.100.42/32"
]
}

When IP allowlisting is enabled:

  • Requests to the Management API from non-allowed IPs receive 403 Forbidden
  • The Ingest API is unaffected — client-side tracking continues to work from any IP
  • Dashboard access can optionally be restricted to the same allowlist

Rotating API keys without downtime follows a three-step process.

  1. Create a new key in the dashboard or via the API. The new key is active immediately
  2. Update your integrations to use the new key. Deploy the changes across all services that reference the old key
  3. Revoke the old key in the dashboard. A 24-hour grace period begins — the old key continues to work during this window to cover staggered deployments

Scheduled rotation

Set up automatic key rotation on a schedule (30, 60, or 90 days) in project settings. The system creates the new key and sends a webhook notification — your CI/CD pipeline handles the swap.

Emergency revocation

If a key is compromised, you can revoke it immediately with no grace period. This is a hard cut — all requests with the revoked key fail instantly.