Authentication
Railhook accepts four credentials. This page shows which one to use, how to get it, and how to replace it without breaking the callers that hold it.
| Credential | Used by | Sent as | Carries |
|---|---|---|---|
| Access token (JWT) | The dashboard and anything acting as a person | Authorization: Bearer … |
A user, their organization, their role |
| API key | Your servers and the SDKs | X-API-Key: … |
One project and a scope |
| Device code | railhook login in the CLI |
Exchanged for a session | Same as an access token |
| Platform admin token | Whoever operates the deployment | X-Platform-Admin-Token: … |
Access to /api/v1/admin/**, across organizations |
Access tokens
Section titled “Access tokens”Sign in with POST /api/v1/auth/login. The response body carries the accessToken. The refresh token does not appear in the body: it is set as an HttpOnly cookie named refresh_token, scoped to /api/v1/auth.
curl -X POST https://railhook.example.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -c cookies.txt \const res = await fetch('https://railhook.example.com/api/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' },});const { accessToken } = await res.json();import os
import requests
res = requests.post( "https://railhook.example.com/api/v1/auth/login",)access_token = res.json()["accessToken"]Send the access token on every request that acts as that user:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Lifetimes and refresh
Section titled “Lifetimes and refresh”| Token | Default lifetime |
|---|---|
| Access token | 15 minutes |
| Refresh token | 24 hours |
POST /api/v1/auth/refresh reads the refresh_token cookie, or a refreshToken field in the JSON body if there is no cookie. It returns a new access token and sets a new refresh cookie. The refresh token you spent is blacklisted.
Each sign-in is a session. A user can list their sessions, close one, or close all of them. POST /api/v1/auth/logout revokes the current access and refresh tokens.
Failed sign-ins
Section titled “Failed sign-ins”Sign-in is rate limited and repeated failures lock the account for a while. Both are configured through the environment:
| Variable | Default | Meaning |
|---|---|---|
AUTH_RATE_LIMIT_LOGIN_PER_MINUTE |
10 |
Sign-in attempts per IP and per email, per minute. Past it the API answers 429. |
AUTH_LOCKOUT_ENABLED |
true |
Turns lockout on or off |
AUTH_LOCKOUT_THRESHOLD |
5 |
Consecutive failures before the first lockout |
AUTH_LOCKOUT_INITIAL_SECONDS |
60 |
First lockout. Doubles with each further failure. |
AUTH_LOCKOUT_MAX_SECONDS |
900 |
The longest a lockout gets |
AUTH_LOCKOUT_FAILURE_WINDOW_MINUTES |
60 |
How far back failures are counted |
A lockout expires on its own, and a password reset clears it straight away.
API keys
Section titled “API keys”An API key belongs to one project. It is what sends events, and what the SDKs are built on.
-
Create the key for a project, in the dashboard or with
POST /api/v1/projects/{projectId}/api-keys. -
Copy the
keyfrom the response. It is shown once: Railhook stores only a hash, so a lost key cannot be recovered, only replaced. -
Put it in an environment variable on a server you control and send it in the
X-API-Keyheader:X-API-Key: Kz1uAIM8VeJUQN7yGSYCst64WxNLabBHfOYbrPlJ1yk
Scopes and expiry
Section titled “Scopes and expiry”| Scope | Allows |
|---|---|
READ_WRITE |
Reads and writes. Sending an event is a write. The default. |
READ_ONLY |
Reads only. A write answers 403. |
A key can carry an expiresAt date. After it, or once the key is revoked, the key authenticates nothing. Revoking takes effect immediately, with no grace period.
Rotate a key without downtime
Section titled “Rotate a key without downtime”POST /api/v1/projects/{projectId}/api-keys/{apiKeyId}/rotate creates a replacement with the same name and scope. The old key keeps working for a grace window, so you can deploy the new one everywhere before the old one stops.
| Field | Default | Meaning |
|---|---|---|
gracePeriodHours |
24 |
How long the old key keeps working. 0 cuts it off now, which is the rotation to run after a leak. |
expiresAt |
none | Optional expiry for the new key |
Rotation never extends the old key: if it was already due to expire sooner, that date stands. A key can be rotated once. To rotate again, rotate its replacement.
CLI sign-in
Section titled “CLI sign-in”railhook login uses a device code. The CLI prints a URL and a code, you approve the code in the dashboard, and the CLI receives a session of its own. See CLI.
Platform admin token
Section titled “Platform admin token”PLATFORM_ADMIN_TOKEN is the credential for the operator endpoints under /api/v1/admin/**. It is independent of organizations: an organization OWNER does not get it. Leave it empty and the admin endpoints stay unreachable. Set it only when an operator needs them, and send it in the X-Platform-Admin-Token header.
The request and response shapes for every auth endpoint are in the API reference.
Sign in with Google
Section titled “Sign in with Google”“Continue with Google” appears on the sign-in and registration pages once the deployment has a Google OAuth client. One button does both jobs:
- A Google account that matches no Railhook account gets one straight away, with the address marked verified and an organization it owns. A Google Workspace account names the organization after its domain (
acme.combecomes Acme); a personal account gets Your name’s workspace. Rename it in organization settings. - An account that signed up with a password and the same address gains Google as a second way in. The password keeps working.
- The account is matched on Google’s permanent id for the person, so changing the address on the Google account still signs in to the same Railhook account.
An account created through Google has no password. To add one, use Forgot password on the sign-in page.
-
In Google Cloud Console, create an OAuth client ID of type Web application.
-
Add the authorized redirect URI. It is your
APP_BASE_URLfollowed by the callback path, and it has to match exactly:https://railhook.example.com/api/v1/auth/oauth/google/callbackFor a local stack on port 8080, add
http://localhost:8080/api/v1/auth/oauth/google/callbackas well. -
On the OAuth consent screen, keep the scopes to
openid,emailandprofile. -
Set both values on the API and restart it:
Terminal window GOOGLE_OAUTH_CLIENT_ID=1234567890-abc.apps.googleusercontent.comGOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-...On Kubernetes, set
googleSignIn.clientIdand put the secret in the Secret named bygoogleSignIn.existingSecret.