Skip to content

Authentication

Whatomate supports two authentication methods:

  1. Session cookies - For user sessions and frontend applications
  2. API Keys - For server-to-server integrations and automation

Logging in (or registering, refreshing, or switching org) sets three cookies and returns only { expires_in, user } in the body. No token is ever returned in the response body.

CookiehttpOnlyPathMax-AgePurpose
whm_accessyes<base_path>/apijwt.access_expiry_minsThe access JWT
whm_refreshyes<base_path>/api/auth/refreshjwt.refresh_expiry_daysThe refresh JWT
whm_csrfno<base_path>/jwt.refresh_expiry_daysRead by JS and echoed back in X-CSRF-Token

All three are SameSite=Lax, and Secure when cookie.secure is set (forced on in production). Because the two auth cookies are httpOnly, JavaScript cannot read them; the browser attaches them automatically.

Cookie-authenticated mutating requests must also send the whm_csrf value in an X-CSRF-Token header — see CSRF.

For non-browser clients, use an API key in the X-API-Key header — those requests skip the CSRF check. An Authorization: Bearer <token> header is accepted only as a fallback.

Create a new user account within an existing organization. Registration requires an organization_id, which is typically provided via an invitation link.

Terminal window
POST /api/auth/register
{
"email": "user@example.com",
"password": "securepassword123",
"full_name": "John Doe",
"organization_id": "uuid"
}
FieldTypeRequiredDescription
emailstringYesUser's email address
passwordstringYesMust be non-empty. The API enforces no minimum length here — see the note below
full_namestringYesDisplay name
organization_idstringYesUUID of the organization to join

Sets the whm_access and whm_refresh httpOnly cookies. The body contains the expiry hint and the full user object — no tokens.

{
"status": "success",
"data": {
"expires_in": 900,
"user": {
"id": "uuid",
"email": "user@example.com",
"full_name": "John Doe",
"organization_id": "uuid",
"role": {
"id": "uuid",
"name": "agent"
}
}
}
}

Authenticate and start a session. On success, the whm_access and whm_refresh httpOnly cookies are set and the body returns the full user object (note the field is full_name, not name).

Terminal window
POST /api/auth/login
{
"email": "user@example.com",
"password": "securepassword12"
}
{
"status": "success",
"data": {
"expires_in": 900,
"user": {
"id": "uuid",
"email": "user@example.com",
"full_name": "John Doe",
"organization_id": "uuid",
"role": {
"id": "uuid",
"name": "agent"
}
}
}
}

Rotate the access token. The endpoint reads the refresh token from the whm_refresh cookie first, falling back to a refresh_token field in the JSON body. On success, both cookies are re-issued.

Terminal window
POST /api/auth/refresh

The body is optional when the whm_refresh cookie is present. Provide it only for non-browser clients:

{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}
{
"status": "success",
"data": {
"expires_in": 900,
"user": {
"id": "uuid",
"email": "user@example.com",
"full_name": "John Doe"
}
}
}

Switch the current user's active organization. Re-issues the auth cookies scoped to the target organization with the user's org-specific role and permissions.

Terminal window
POST /api/auth/switch-org
{
"organization_id": "uuid"
}
{
"status": "success",
"data": {
"expires_in": 900,
"user": {
"id": "uuid",
"email": "user@example.com",
"full_name": "John Doe",
"organization_id": "uuid",
"role": {
"id": "uuid",
"name": "agent",
"permissions": [
{ "resource": "contacts", "action": "read" },
{ "resource": "chat", "action": "read" }
]
}
}
}
}

Clear the session. Revokes the refresh token (read from the whm_refresh cookie, or the body as a fallback) and clears both auth cookies.

Terminal window
POST /api/auth/logout
{
"status": "success",
"data": {
"status": "logged_out"
}
}

Because httpOnly cookies cannot be read by JavaScript, the real-time WebSocket connection is authenticated with a short-lived token obtained here.

Terminal window
GET /api/auth/ws-token
{
"status": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs..."
}
}

The token is a JWT with subject ws and a 30-second lifetime — fetch a fresh one for every connection attempt, including reconnects.

/ws upgrades without authentication. The token is never put in the URL; instead the client sends it as the first message on the open socket, and the server registers the connection only after validating it:

{ "type": "auth", "payload": { "token": "eyJhbGciOiJIUzI1NiIs..." } }

Returns the enabled SSO providers (deduplicated by type) so a login page can render the available buttons. Public — no authentication required.

Terminal window
GET /api/auth/sso/providers
{
"status": "success",
"data": [
{ "provider": "google", "name": "Google" },
{ "provider": "custom", "name": "Custom" }
]
}

Redirects the browser to the identity provider's OAuth authorization page. Open this URL directly in the browser (not via XHR).

Terminal window
GET /api/auth/sso/{provider}/init

The provider redirects back here after the user authenticates. On success the endpoint sets the auth cookies and redirects into the app.

Terminal window
GET /api/auth/sso/{provider}/callback

Browser clients need no extra headers — the httpOnly cookies are sent automatically. For programmatic access, send an API key instead:

Terminal window
curl -X GET "http://your-server:8080/api/contacts" \
-H "X-API-Key: whm_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
Token TypeConfig keyDefault
Access tokenjwt.access_expiry_mins15 minutes
Refresh tokenjwt.refresh_expiry_days1 day

expires_in in the response body is access_expiry_mins × 60900 with the defaults.

When the access token expires, call POST /api/auth/refresh to rotate it. Both durations are configurable in config.toml.