Skip to content
Back to site

Authentication

Railhook accepts four credentials.

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

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. The cookie is Secure when APP_ENV=production or APP_BASE_URL starts with https://, and SameSite=Strict in production (Lax otherwise).

Terminal window
curl -X POST https://railhook.example.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email":"you@company.com","password":"••••••••"}'

Send the access token on every request that acts as that user:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
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. The dashboard retries a refresh that fails with 429 or a 5xx and signs out only when the session itself is rejected.

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.

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_RATE_LIMIT_REFRESH_PER_TOKEN_PER_MINUTE 30 Session refreshes per refresh token, per minute. Refresh has its own budget and never spends the sign-in one.
AUTH_RATE_LIMIT_REFRESH_PER_IP_PER_MINUTE 600 Session refreshes per IP, per minute
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.

An API key belongs to one project. It is what sends events, and what the SDKs are built on.

  1. Create the key for a project, in the dashboard or with POST /api/v1/projects/{projectId}/api-keys.

  2. Copy the key from the response. It is shown once: Railhook stores only a hash, so a lost key cannot be recovered, only replaced.

  3. Put it in an environment variable on a server you control and send it in the X-API-Key header:

    X-API-Key: Kz1uAIM8VeJUQN7yGSYCst64WxNLabBHfOYbrPlJ1yk
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.

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.

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 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.

Open Settings → Profile → Change email. What happens depends on whether the account has verified the address it has now.

  • Not verified yet. The address changes at once, and a new verification link goes to it. The link sent to the old address stops working. The account stays unverified, with the same restrictions, until the new address is verified. When the deployment has a CAPTCHA, the change asks for it, as registration does. The banner that asks you to verify offers the same form under Wrong address? Change it.
  • Verified. Enter your password. An account that signs in only with Google has no password: sign in with Google again, then change the address within 10 minutes. Nothing changes until someone opens the link sent to the new address. The old address gets a notice with a This wasn’t me link that cancels the change. Until then, Settings shows the address waiting for confirmation, with Resend link and Cancel change.

Confirming the change, or cancelling it from the old address, signs out every session. Links work once and expire after 24 hours. An address that already belongs to an account is refused with the same answer registration gives. EMAIL_SUPPORT_ADDRESS sets the contact the notice to the old address gives; see the configuration reference.

The organization, its plan and its usage stay as they are, and a linked Google account stays linked. Each request, confirmation, cancellation and limit hit is written to the audit log of every organization the person belongs to, so an owner sees a member’s address change.

Email addresses are not case-sensitive: Ada@Company.com and ada@company.com are the same account, for registration, sign-in, invites, password reset, email change and Google sign-in alike. Addresses are stored in lower case.

Registration, member invites and the billing email refuse an address whose ending cannot receive mail, such as gmail.con, and offer the address that was probably meant.

“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.com becomes 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.

  1. In Google Cloud Console, create an OAuth client ID of type Web application.

  2. Add the authorized redirect URI. It is your APP_BASE_URL followed by the callback path, and it has to match exactly:

    https://railhook.example.com/api/v1/auth/oauth/google/callback

    For a local stack on port 8080, add http://localhost:8080/api/v1/auth/oauth/google/callback as well.

  3. On the OAuth consent screen, keep the scopes to openid, email and profile.

  4. Set both values on the API and restart it:

    Terminal window
    GOOGLE_OAUTH_CLIENT_ID=1234567890-abc.apps.googleusercontent.com
    GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-...

    On Kubernetes, set googleSignIn.clientId and put the secret in the Secret named by googleSignIn.existingSecret.