Skip to content

Configuration ​

AsyncUp is configured in two layers:

  1. Bootstrap: a handful of environment variables (database, port, the operator token, the encryption key). Set once, rarely touched. This page is the one reference for them.
  2. Everything else: managed in the web app under Settings and stored in your database, secrets encrypted (AES-256-GCM via SECRET_KEY). Changes apply immediately, no restart.

Environment variables ​

VariableDefaultPurpose
PORT8080Webhook, API and web app port
DB_PATH./data/standup.dbSQLite database file (default storage). The Docker image sets /data/standup.db
DATABASE_URL(empty)Bring-your-own PostgreSQL — when set, SQLite is skipped (see Deployment reference)
DB_SSLfrom URL sslmodePostgres TLS: require (encrypt, no verify — default for managed DBs), verify-full, or disable
DB_SSL_CA(empty)CA bundle path for DB_SSL=verify-full
OPERATOR_TOKEN(empty)Operator token for the web app and the JSON API — the way in before Google or SAML sign-in works, and break-glass after. Disabled while empty. DASHBOARD_TOKEN is the older name and still works; when both are set, OPERATOR_TOKEN wins, and a blank OPERATOR_TOKEN= falls back to DASHBOARD_TOKEN
SECRET_KEY—Encrypts stored secrets; signs sessions and webhooks. Generate with openssl rand -hex 32. Required (except ADAPTER=fake). Back it up separately from the database
ADAPTERgooglegoogle for production, fake for a console demo
TENANT_IDdefaultTenant identifier — leave as is for self-hosted installs

Changing a variable needs the container recreated, not just restarted: docker compose up -d --force-recreate asyncup.

App settings (stored in the database) ​

Open https://HOST/app → Settings. Each value saves on its own. Secrets are write-only: the UI shows that they're set (and, for the service account, its email), never the value.

WhereSettingWhat it does
GeneralDefault timezone for new standupsAssigned to new standups; people can set their own
GeneralCalendar out-of-officeMark people away on OOO days. Off by default; turn on once the delegation for the Calendar scope exists
GeneralLeave in Chat and CalendarDays off and vacation mode as a Chat status and an out-of-office event. Off by default; needs two delegated scopes
GeneralSpelling suggestionsLikely typos in Chat answers pointed out once before posting, in British or American English, with a list of words to accept. Off by default. See below
Google ChatProject number (and the events URL)The audience incoming events are verified against. For an add-on Chat app both are needed — the audience rule
Google ChatService accountThe JSON key used for Chat API calls, group rosters, Directory lookups and Calendar. Empty = Application Default Credentials for Chat only
Google ChatDirectory through delegation (legacy)Optional Workspace admin to act as: moves Directory lookups to domain-wide delegation. Leave it off with the admin role; Calendar out-of-office doesn't use it. The Client ID for domain-wide delegation shows under Service account
Sign-in & SSOGoogle sign-inOAuth client ID and secret — Google sign-in
Sign-in & SSOSAML single sign-onIdP entity ID, SSO URL, certificate, admin attribute (default groups) and admin group (default asyncup-admins) — SAML
Sign-in & SSOSetup tokenWhether the operator token still signs in. Can only be switched off while Google or SAML works
MCP serverServer, default scopes, access tokensClaude Code and MCP
API & tokensMachine tokensTick (POST /tick), CSV export (GET /export) and SCIM (/scim/v2) tokens, each shown once
Danger zoneDisconnect Google Chat; Delete all standup historyBoth behind a typed confirmation

Spelling suggestions ​

The checker is Harper (Apache-2.0). It runs on your server, in its own process: nothing is sent anywhere. That process starts at the first answer, uses about 350 MB while it runs, and stops after 30 minutes without an answer, giving the memory back. It adds about 72 MB to the installed packages.

It checks spelling and typos only, never grammar or style. Names on any roster are accepted, and admins can add words under Words to accept, one per line. If the checker can't start within ten seconds, or doesn't answer within two, the answer is saved unchecked: spelling never blocks anyone. The web app's page language follows the same setting, so the browser's own spell check uses it too.

Per-standup settings (schedule, questions, mood, people, roster group, escalation, webhook, digest, archive and delete) live on each standup's Settings tab and in the chat commands.

Endpoints ​

All token comparisons are constant-time. ✱ = covered by the shared rate limiter (60 requests a minute per IP).

EndpointAuthPurpose
GET /healthznoneLiveness: pings the database
GET /health/chatnoneChat connection state without secrets: audience and key set, last verified or refused event
POST /chat/eventsGoogle-signed JWT, verified against the audienceGoogle Chat events — point the Chat app here. Refuses everything until the audience is set
POST /tick ✱Bearer tick token (open until one exists)Advance the scheduler from an external cron (scale-to-zero)
GET /export?standupId=N&days=D ✱Bearer export token (404 until one exists)CSV, D clamped to 1–365
GET /app[/…]Session cookie, or the operator token entered on the sign-in pageThe web app, including the guided setup at /app/setup and the personal page at /app/me
/api/v1/… (300 requests a minute)Session cookie, or bearer operator token; auth/methods and openapi.json are publicThe JSON API; OpenAPI 3.1 at GET /api/v1/openapi.json
/mcp ✱Bearer MCP token (503 while switched off; browser Origin refused)Model Context Protocol for Claude Code and other assistants
GET /auth/google, GET /auth/callback, POST /auth/logout ✱OAuth state nonceGoogle sign-in
GET /auth/saml, POST /auth/saml/acs, GET /auth/saml/metadata ✱Signed assertionsSAML sign-in and SP metadata (404 until the IdP is configured)
/scim/v2/ServiceProviderConfig, /scim/v2/Users[…] ✱Bearer SCIM token (404 until one exists)SCIM 2.0

Outbound webhooks ​

Give a standup a Webhook URL (Standup › Settings › Integrations) and AsyncUp POSTs JSON to it as things happen — the cheap path into Sheets (Apps Script), Zapier/n8n, or your own service:

EventWhenBody
submissionEvery submission and editstandup, date, user, answers, mood, late, edited, editApprovedBy (who approved an edit after the deadline, otherwise null). Mood comments are never sent
wrap_upThe run closesstandup, date, summary (the posted wrap-up's numbers, plus teamMood on anonymous standups)
schedule_changeSomeone's days on the standup, or their days off, changeperson, summary, who, channel — see Personal schedules
time_off_requestA request needs approvalperson, dates, working, reason, request ids
poll_closedA poll with questions closespoll: id, title, anonymous, asked, answered and each question's counts per option, with names only on a named poll
testSend test in the standup's settingsstandup

Deliveries time out after 5 s and failures are only logged — a dead webhook never breaks the standup.

Every delivery is signed: the X-AsyncUp-Signature header carries sha256=<hex>, the HMAC-SHA256 of the raw request body with the standup's signing secret. Copy it from the standup's Settings → Outbound webhook → Show signing secret (admins and that standup's managers), or fetch it with GET /api/v1/standups/:id/webhook/secret. It is derived from SECRET_KEY and the standup's id, so it changes only when SECRET_KEY does.

Verify it in your receiver and reject anything that doesn't match:

js
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));

Data ​

All state — standups, participants, admins, runs, submissions, blockers, app settings — lives either in a single SQLite file (DB_PATH, the default) or in your own PostgreSQL (DATABASE_URL). Schema migrations run automatically on startup in both modes. Backups and upgrades: Operations.

Managed Postgres TLS: RDS / Cloud SQL / Neon / Supabase present a certificate signed by their own CA. AsyncUp treats sslmode=require as "encrypt but don't verify the cert" (the libpq default every other client uses), so DATABASE_URL=…?sslmode=require connects out of the box. For strict verification set DB_SSL=verify-full and point DB_SSL_CA at the provider's CA bundle.

Stored secrets are encrypted, so backups are safe to ship off-box as long as SECRET_KEY stays out of them. Graceful shutdown on SIGTERM included.

Released under the MIT License.