Open-source WhatsApp claims-intake bot and support dashboard for insurers in emerging markets. Self-hosted (Go, Next.js, Postgres, Redis), bring your own WhatsApp number and AI key, 15 languages.
See the codeRapidOS is a self-hosted, multi-tenant WhatsApp insurance-claims assistant with a Go API, Next.js dashboard, PostgreSQL, Redis, and configurable LLM providers.
.env.example to .env and replace every placeholder. Generate ENCRYPTION_KEY with openssl rand -base64 32.docker compose up --build.http://localhost:3000/signup (or click Create account on the home page) to create a company.
The wizard asks for company and contact details, the coverage types you offer (seeded with default required
information and documents), assistant on/off switches and the assistant and dashboard language (15 languages, see Languages).
No password is chosen: the server generates the super-admin password (a UUID) and shows it once on the
success screen. Signups are rate-limited per IP (SIGNUP_RATE_LIMIT_PER_HOUR, default 5).<base>/api/v1/whatsapp/webhook; Test webhook runs Meta's verification
handshake against the API with the saved token.lib/users/roles.ts); the legacy admin_permissions
table is unused. Resetting a password or deactivating an account signs that user out everywhere at once:
every token carries the account's token_version, which the reset bumps, so the old session gets 401
on its next request. Viewers never see create/edit/delete controls, and the API rejects their writes.
There is no public self-registration: accounts come only from /signup (a new company and its super
admin) and the Users page. Emails are case-insensitive (stored lowercased).The Go API serves Meta's webhook at the fixed path /api/v1/whatsapp/webhook and matches the GET challenge
against each company's saved verify token, so a token change takes effect immediately.
Requires Docker Desktop (or Docker Engine with Compose v2).
.env is gitignored):
cp .env.example .env # PowerShell: Copy-Item .env.example .env
Use long random values for POSTGRES_PASSWORD, REDIS_PASSWORD, JWT_ACCESS_SECRET,
JWT_REFRESH_SECRET and INTERNAL_API_KEY (openssl rand -hex 32), and a base64 32-byte key for
ENCRYPTION_KEY (openssl rand -base64 32). LLM and WhatsApp keys can stay blank; they are entered
per company in Settings.docker compose up -d --build
docker compose ps # postgres, redis, api, dashboard should be "healthy"
The one-shot migrate service runs prisma migrate deploy before the API and dashboard start.
There is no demo seed; /signup creates a company, its super-admin and default data.localhost:5432, Redis localhost:6379 (bound to 127.0.0.1 only)docker compose --profile tools up -d adminer then http://localhost:8081docker compose logs -f api dashboard # follow logs
docker compose down # stop (data is kept)
docker compose down -v # stop and wipe the database/redis volumes (full reset)
LLM per company: each company picks its model in Settings > Integrations (Gemini, OpenAI,
DeepSeek, Qwen or any OpenAI-compatible server such as Ollama or vLLM). No global LLM_API_KEY is
needed; the LLM_* variables are only a fallback for companies that saved no LLM settings. Changes take
effect on the next message (the client is rebuilt when the settings change).
LLM_TIMEOUT (seconds, default 90) bounds every LLM request. Raise it for large local models on
slow hardware.http://host.docker.internal:11434/v1
(from the containers) and any API key value. Ollama unloads an idle model after 5 minutes, so the next
customer waits for a cold load (10-30 s for 7-14B models). Set OLLAMA_KEEP_ALIVE on the machine
running Ollama (for example OLLAMA_KEEP_ALIVE=24h, or -1 to keep it loaded forever) and restart
Ollama to keep the model in memory.Testing WhatsApp: Meta must reach the backend over a public HTTPS URL. Run e.g. ngrok http 8080,
paste its https:// address into Settings > WhatsApp integration > Public API base URL (or set
PUBLIC_API_URL in .env as the default for every company), save, then copy the callback URL and verify
token from that card into the Meta developer app (WhatsApp > Configuration > Webhook > Edit) and subscribe
to the messages field. Use Meta's free test phone number and add your own number as a test recipient.
Claim documents and PDFs: uploaded files, WhatsApp photos/documents and generated claim PDFs are
stored on disk under FILE_STORAGE_DIR (/app/storage, the dashboard_files Docker volume), in
companies/<companyId>/claims/<claimId>/. Every read checks the company of the signed-in admin. Back up
that volume together with the database (docker compose down -v deletes it).
/api/claims/<claimNumber>/pdf (?download=1, ?regenerate=1, ?lang=fr)./public path or a public https URL up to 1 MB; private-network URLs are refused unless
PDF_LOGO_ALLOW_PRIVATE=true).PDF_FONT_DIR) renders accents, Greek, Cyrillic
and Vietnamese; Amharic, Hindi and Bengali are not covered by it and fall back to English.POST /api/media/upload with the internal API key and the
company id (X-Internal-API-Key, X-Company-ID); the customer must belong to that company. Media sent
while a claim is being filed is kept and attached when the claim is created.WHATSAPP_GRAPH_API_BASE overrides the Meta Graph API base (default https://graph.facebook.com/v18.0),
e.g. to point at a mock server in tests.The home page (/), the documentation (/docs, including the API reference at /docs/api) /pricing and /contact are
public pages for visitors. They are written in English only, on purpose: they use no i18n dictionary and no
language switcher, keep lang="en" and leave browser auto-translation enabled. The dashboard keeps its 15 languages.
config/site.ts (the URL comes from NEXT_PUBLIC_BASE_URL).config/pricing.ts. No prices are published
and no payment provider is connected./contact: submissions are stored in the contact_requests table (no e-mail is sent). The contact address
is contactEmail in config/site.ts.lib/marketing/api/ (written from the route code; __tests__/unit/apiReference.test.ts fails when a
route is added or removed without updating it).public/screenshots/ (taken from a demo company with fictional data), listed in lib/marketing/screenshots.ts.The dashboard and the WhatsApp assistant support 15 languages. Each company picks a dashboard language and an
assistant language (at signup or in Settings > Language), and each user can override the dashboard
language for themselves. Arabic is right-to-left: the page gets dir="rtl" and the layout is mirrored.
| Code | Language | Native name | Dir | Main regions | Status |
|---|---|---|---|---|---|
en | English | English | ltr | — | Reviewed (source language) |
fr | French | Français | ltr | DRC, West and Central Africa, France | Reviewed |
pt | Portuguese | Português | ltr | Angola, Mozambique, Brazil | Machine-translated, community review needed |
es | Spanish | Español | ltr | Latin America | Machine-translated, community review needed |
ar | Arabic | العربية | rtl | North Africa, Middle East | Machine-translated, community review needed |
sw | Swahili | Kiswahili | ltr | Kenya, Tanzania, Uganda, eastern DRC | Machine-translated, community review needed |
vi | Vietnamese | Tiếng Việt | ltr | Vietnam | Machine-translated, community review needed |
id | Indonesian | Bahasa Indonesia | ltr | Indonesia | Machine-translated, community review needed |
hi | Hindi | हिन्दी | ltr | India | Machine-translated, community review needed |
bn | Bengali | বাংলা | ltr | Bangladesh, India | Machine-translated, community review needed |
tl | Filipino | Filipino | ltr | Philippines | Machine-translated, community review needed |
am | Amharic | አማርኛ | ltr | Ethiopia | Machine-translated, community review needed |
ha | Hausa | Hausa | ltr | Nigeria, Niger, Ghana | Machine-translated, community review needed |
yo | Yoruba | Yorùbá | ltr | Nigeria, Benin | Machine-translated, community review needed |
ln | Lingala | Lingála | ltr | DRC, Republic of the Congo | Machine-translated, community review needed |
Coverage. English and French are complete. The other locales cover the core screens: sign-in, signup,
home, navigation, the claims, customers, conversations and analysis lists, and the main Settings sections.
Any missing string falls back to English. Run npm run check:locales to see the current coverage per locale.
The WhatsApp status-update messages and the assistant's built-in replies are translated for all 15 languages.
Machine-translated locales are marked as such in the language pickers until a native speaker reviews them.
Assistant language. English and French have full, hand-written default prompts. Every other language uses the English base prompt plus a rule telling the model to answer in the company's language. The Reply in the customer's language switch (Settings > Assistant & claims behaviour, on by default) lets the assistant detect when a customer writes in another language and answer in that language instead. Turn it off to always answer in the company's language. Status-update messages use the template for the company's assistant language, or English if there is none.
LOCALE_REGISTRY in lib/i18n/locales.ts with its code (ISO 639-1
where possible), name, nativeName, dir (ltr or rtl), intl (the locale used for dates and
numbers, e.g. pt-BR), reviewed: false and regions. The pickers, validation and database accept any
registered code; no migration is needed (language columns are VARCHAR(10)).lib/i18n/locales/en.json to lib/i18n/locales/<code>.json and translate
the values. You can delete keys you have not translated yet; they fall back to English. Keep every
{placeholder} exactly as it is, and don't translate the keys.backend/internal/service/botlocales/en.json to botlocales/<code>.json and
translate all of its keys. languageName must equal the registry name and nativeName must equal
the registry nativeName. The Go API embeds these files at build time.npm run check:locales (add --strict to fail on missing dashboard keys),
cd backend && go test ./... and npx tsc --noEmit. The checker verifies that every registered locale
has both files, that there are no unknown or empty keys, that placeholders match English, and that the
assistant file is complete.ms-*, me-*, ps-*, pe-*, start-*,
end-*, text-start, border-s) instead of ml-*, mr-*, left-* or text-left in new UI code, so
the layout mirrors automatically.To review a machine translation, edit the JSON files and set reviewed: true in the registry once a native
speaker has checked the whole dashboard file.
Run npx prisma migrate deploy before rollout and back up the database first. Keep ENCRYPTION_KEY stable: changing it makes stored secrets unreadable. Set CORS_ALLOWED_ORIGINS for the Go API to your dashboard origin(s); never expose secrets through NEXT_PUBLIC_ variables. WhatsApp and LLM environment variables are optional single-tenant fallbacks only.
Rate limits and client IP. Sign-in is limited to 10 attempts per 15 minutes per client IP, plus a
per-account lockout (LOGIN_MAX_FAILURES, default 5 failures, then LOGIN_LOCKOUT_MINUTES, default 15).
Company signups are limited per IP (SIGNUP_RATE_LIMIT_PER_HOUR). By default the client IP is the TCP
peer address (stamped by scripts/socket-ip.cjs, which npm start, npm run dev and the Docker image
preload), and X-Forwarded-For is ignored, so a client cannot dodge the limit by sending a fake header.
Behind a reverse proxy or load balancer set TRUSTED_PROXY=true (one proxy) or TRUSTED_PROXY=<hops>;
the client IP is then taken from X-Forwarded-For, counting that many trusted hops from the right. Only
set it when the dashboard cannot be reached except through those proxies.
Run cd backend; go build ./...; go vet ./...; go test ./..., npm install; npx tsc --noEmit and
npm run check:locales (all locales have valid keys and placeholders).
RapidOS is free software, licensed under the GNU Affero General Public License v3.0 only (AGPL-3.0-only): see LICENSE for the full text and NOTICE.
Copyright (C) 2026 Raph Mwanza raphmwanza5@gmail.com
Under section 13 of the AGPL, if you run a modified version for users over a network you must offer them its
source code. The marketing site footer links to the license and the source code, and the sign-in page links to the
source code, at https://github.com/raphmwanza/RapidOS-open-source; if you publish a modified version, set
githubUrl in config/site.ts to your own repository. The RapidOS name and logo are not covered by the license: see
TRADEMARK.md.
TypeScript
66.3%
Go
32.1%
Open-source WhatsApp claims-intake bot and support dashboard for insurers in emerging markets. Self-hosted (Go, Next.js, Postgres, Redis), bring your own WhatsApp number and AI key, 15 languages.
See the codeRapidOS is a self-hosted, multi-tenant WhatsApp insurance-claims assistant with a Go API, Next.js dashboard, PostgreSQL, Redis, and configurable LLM providers.
.env.example to .env and replace every placeholder. Generate ENCRYPTION_KEY with openssl rand -base64 32.docker compose up --build.http://localhost:3000/signup (or click Create account on the home page) to create a company.
The wizard asks for company and contact details, the coverage types you offer (seeded with default required
information and documents), assistant on/off switches and the assistant and dashboard language (15 languages, see Languages).
No password is chosen: the server generates the super-admin password (a UUID) and shows it once on the
success screen. Signups are rate-limited per IP (SIGNUP_RATE_LIMIT_PER_HOUR, default 5).<base>/api/v1/whatsapp/webhook; Test webhook runs Meta's verification
handshake against the API with the saved token.lib/users/roles.ts); the legacy admin_permissions
table is unused. Resetting a password or deactivating an account signs that user out everywhere at once:
every token carries the account's token_version, which the reset bumps, so the old session gets 401
on its next request. Viewers never see create/edit/delete controls, and the API rejects their writes.
There is no public self-registration: accounts come only from /signup (a new company and its super
admin) and the Users page. Emails are case-insensitive (stored lowercased).The Go API serves Meta's webhook at the fixed path /api/v1/whatsapp/webhook and matches the GET challenge
against each company's saved verify token, so a token change takes effect immediately.
Requires Docker Desktop (or Docker Engine with Compose v2).
.env is gitignored):
cp .env.example .env # PowerShell: Copy-Item .env.example .env
Use long random values for POSTGRES_PASSWORD, REDIS_PASSWORD, JWT_ACCESS_SECRET,
JWT_REFRESH_SECRET and INTERNAL_API_KEY (openssl rand -hex 32), and a base64 32-byte key for
ENCRYPTION_KEY (openssl rand -base64 32). LLM and WhatsApp keys can stay blank; they are entered
per company in Settings.docker compose up -d --build
docker compose ps # postgres, redis, api, dashboard should be "healthy"
The one-shot migrate service runs prisma migrate deploy before the API and dashboard start.
There is no demo seed; /signup creates a company, its super-admin and default data.localhost:5432, Redis localhost:6379 (bound to 127.0.0.1 only)docker compose --profile tools up -d adminer then http://localhost:8081docker compose logs -f api dashboard # follow logs
docker compose down # stop (data is kept)
docker compose down -v # stop and wipe the database/redis volumes (full reset)
LLM per company: each company picks its model in Settings > Integrations (Gemini, OpenAI,
DeepSeek, Qwen or any OpenAI-compatible server such as Ollama or vLLM). No global LLM_API_KEY is
needed; the LLM_* variables are only a fallback for companies that saved no LLM settings. Changes take
effect on the next message (the client is rebuilt when the settings change).
LLM_TIMEOUT (seconds, default 90) bounds every LLM request. Raise it for large local models on
slow hardware.http://host.docker.internal:11434/v1
(from the containers) and any API key value. Ollama unloads an idle model after 5 minutes, so the next
customer waits for a cold load (10-30 s for 7-14B models). Set OLLAMA_KEEP_ALIVE on the machine
running Ollama (for example OLLAMA_KEEP_ALIVE=24h, or -1 to keep it loaded forever) and restart
Ollama to keep the model in memory.Testing WhatsApp: Meta must reach the backend over a public HTTPS URL. Run e.g. ngrok http 8080,
paste its https:// address into Settings > WhatsApp integration > Public API base URL (or set
PUBLIC_API_URL in .env as the default for every company), save, then copy the callback URL and verify
token from that card into the Meta developer app (WhatsApp > Configuration > Webhook > Edit) and subscribe
to the messages field. Use Meta's free test phone number and add your own number as a test recipient.
Claim documents and PDFs: uploaded files, WhatsApp photos/documents and generated claim PDFs are
stored on disk under FILE_STORAGE_DIR (/app/storage, the dashboard_files Docker volume), in
companies/<companyId>/claims/<claimId>/. Every read checks the company of the signed-in admin. Back up
that volume together with the database (docker compose down -v deletes it).
/api/claims/<claimNumber>/pdf (?download=1, ?regenerate=1, ?lang=fr)./public path or a public https URL up to 1 MB; private-network URLs are refused unless
PDF_LOGO_ALLOW_PRIVATE=true).PDF_FONT_DIR) renders accents, Greek, Cyrillic
and Vietnamese; Amharic, Hindi and Bengali are not covered by it and fall back to English.POST /api/media/upload with the internal API key and the
company id (X-Internal-API-Key, X-Company-ID); the customer must belong to that company. Media sent
while a claim is being filed is kept and attached when the claim is created.WHATSAPP_GRAPH_API_BASE overrides the Meta Graph API base (default https://graph.facebook.com/v18.0),
e.g. to point at a mock server in tests.The home page (/), the documentation (/docs, including the API reference at /docs/api) /pricing and /contact are
public pages for visitors. They are written in English only, on purpose: they use no i18n dictionary and no
language switcher, keep lang="en" and leave browser auto-translation enabled. The dashboard keeps its 15 languages.
config/site.ts (the URL comes from NEXT_PUBLIC_BASE_URL).config/pricing.ts. No prices are published
and no payment provider is connected./contact: submissions are stored in the contact_requests table (no e-mail is sent). The contact address
is contactEmail in config/site.ts.lib/marketing/api/ (written from the route code; __tests__/unit/apiReference.test.ts fails when a
route is added or removed without updating it).public/screenshots/ (taken from a demo company with fictional data), listed in lib/marketing/screenshots.ts.The dashboard and the WhatsApp assistant support 15 languages. Each company picks a dashboard language and an
assistant language (at signup or in Settings > Language), and each user can override the dashboard
language for themselves. Arabic is right-to-left: the page gets dir="rtl" and the layout is mirrored.
| Code | Language | Native name | Dir | Main regions | Status |
|---|---|---|---|---|---|
en | English | English | ltr | — | Reviewed (source language) |
fr | French | Français | ltr | DRC, West and Central Africa, France | Reviewed |
pt | Portuguese | Português | ltr | Angola, Mozambique, Brazil | Machine-translated, community review needed |
es | Spanish | Español | ltr | Latin America | Machine-translated, community review needed |
ar | Arabic | العربية | rtl | North Africa, Middle East | Machine-translated, community review needed |
sw | Swahili | Kiswahili | ltr | Kenya, Tanzania, Uganda, eastern DRC | Machine-translated, community review needed |
vi | Vietnamese | Tiếng Việt | ltr | Vietnam | Machine-translated, community review needed |
id | Indonesian | Bahasa Indonesia | ltr | Indonesia | Machine-translated, community review needed |
hi | Hindi | हिन्दी | ltr | India | Machine-translated, community review needed |
bn | Bengali | বাংলা | ltr | Bangladesh, India | Machine-translated, community review needed |
tl | Filipino | Filipino | ltr | Philippines | Machine-translated, community review needed |
am | Amharic | አማርኛ | ltr | Ethiopia | Machine-translated, community review needed |
ha | Hausa | Hausa | ltr | Nigeria, Niger, Ghana | Machine-translated, community review needed |
yo | Yoruba | Yorùbá | ltr | Nigeria, Benin | Machine-translated, community review needed |
ln | Lingala | Lingála | ltr | DRC, Republic of the Congo | Machine-translated, community review needed |
Coverage. English and French are complete. The other locales cover the core screens: sign-in, signup,
home, navigation, the claims, customers, conversations and analysis lists, and the main Settings sections.
Any missing string falls back to English. Run npm run check:locales to see the current coverage per locale.
The WhatsApp status-update messages and the assistant's built-in replies are translated for all 15 languages.
Machine-translated locales are marked as such in the language pickers until a native speaker reviews them.
Assistant language. English and French have full, hand-written default prompts. Every other language uses the English base prompt plus a rule telling the model to answer in the company's language. The Reply in the customer's language switch (Settings > Assistant & claims behaviour, on by default) lets the assistant detect when a customer writes in another language and answer in that language instead. Turn it off to always answer in the company's language. Status-update messages use the template for the company's assistant language, or English if there is none.
LOCALE_REGISTRY in lib/i18n/locales.ts with its code (ISO 639-1
where possible), name, nativeName, dir (ltr or rtl), intl (the locale used for dates and
numbers, e.g. pt-BR), reviewed: false and regions. The pickers, validation and database accept any
registered code; no migration is needed (language columns are VARCHAR(10)).lib/i18n/locales/en.json to lib/i18n/locales/<code>.json and translate
the values. You can delete keys you have not translated yet; they fall back to English. Keep every
{placeholder} exactly as it is, and don't translate the keys.backend/internal/service/botlocales/en.json to botlocales/<code>.json and
translate all of its keys. languageName must equal the registry name and nativeName must equal
the registry nativeName. The Go API embeds these files at build time.npm run check:locales (add --strict to fail on missing dashboard keys),
cd backend && go test ./... and npx tsc --noEmit. The checker verifies that every registered locale
has both files, that there are no unknown or empty keys, that placeholders match English, and that the
assistant file is complete.ms-*, me-*, ps-*, pe-*, start-*,
end-*, text-start, border-s) instead of ml-*, mr-*, left-* or text-left in new UI code, so
the layout mirrors automatically.To review a machine translation, edit the JSON files and set reviewed: true in the registry once a native
speaker has checked the whole dashboard file.
Run npx prisma migrate deploy before rollout and back up the database first. Keep ENCRYPTION_KEY stable: changing it makes stored secrets unreadable. Set CORS_ALLOWED_ORIGINS for the Go API to your dashboard origin(s); never expose secrets through NEXT_PUBLIC_ variables. WhatsApp and LLM environment variables are optional single-tenant fallbacks only.
Rate limits and client IP. Sign-in is limited to 10 attempts per 15 minutes per client IP, plus a
per-account lockout (LOGIN_MAX_FAILURES, default 5 failures, then LOGIN_LOCKOUT_MINUTES, default 15).
Company signups are limited per IP (SIGNUP_RATE_LIMIT_PER_HOUR). By default the client IP is the TCP
peer address (stamped by scripts/socket-ip.cjs, which npm start, npm run dev and the Docker image
preload), and X-Forwarded-For is ignored, so a client cannot dodge the limit by sending a fake header.
Behind a reverse proxy or load balancer set TRUSTED_PROXY=true (one proxy) or TRUSTED_PROXY=<hops>;
the client IP is then taken from X-Forwarded-For, counting that many trusted hops from the right. Only
set it when the dashboard cannot be reached except through those proxies.
Run cd backend; go build ./...; go vet ./...; go test ./..., npm install; npx tsc --noEmit and
npm run check:locales (all locales have valid keys and placeholders).
RapidOS is free software, licensed under the GNU Affero General Public License v3.0 only (AGPL-3.0-only): see LICENSE for the full text and NOTICE.
Copyright (C) 2026 Raph Mwanza raphmwanza5@gmail.com
Under section 13 of the AGPL, if you run a modified version for users over a network you must offer them its
source code. The marketing site footer links to the license and the source code, and the sign-in page links to the
source code, at https://github.com/raphmwanza/RapidOS-open-source; if you publish a modified version, set
githubUrl in config/site.ts to your own repository. The RapidOS name and logo are not covered by the license: see
TRADEMARK.md.
TypeScript
66.3%
Go
32.1%