Reference

Troubleshooting

Updated

The greatest hits. If your problem is not here, open an issue at https://github.com/ArnasDon/wacrm/issues.

Build-time

Error: ENCRYPTION_KEY must be 64 hex chars

ENCRYPTION_KEY is missing or not the right length. Generate one:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Put the output into .env.local / your production env.

Error: supabaseUrl is required

NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY is not set at build time. On Hostinger / any Node host, make sure the env vars are exported before npm run build runs.

Next.js complains about missing framework docs

The repo's AGENTS.md pins contributors to the docs bundled in node_modules/next/dist/docs/ — that notice is for humans editing the code, it does not affect your build.

Auth

Sign-up succeeds but the confirmation email never arrives

In Supabase → Authentication → Providers → Email, either:

  • Configure a custom SMTP provider (recommended for production), or
  • Temporarily turn off Confirm email while testing.

The app asks Supabase to send every emailed link back to the origin the browser loaded the page from (https://crm.example.com/auth/callback?…). Supabase only honours that request when the origin is on its Redirect URLs allow-list; otherwise it silently falls back to the Site URL, whose default is http://localhost:3000. No app-side environment variable changes this — it is purely Supabase configuration.

Hosted Supabase → Authentication → URL Configuration:

  • Site URL: https://crm.example.com
  • Redirect URLs: https://crm.example.com/** (the /** matters — the link carries a path and query string). Add http://localhost:3000/** too if you also run the app locally.

Self-hosted Supabase (Docker): the Studio page for URL Configuration does not persist. Set SITE_URL and ADDITIONAL_REDIRECT_URLS in the .env next to docker-compose.yml and restart the auth container.

Emails already sent keep their old link — request a fresh one to test. The full write-up, including the token-hash templates that make links work across devices, is in the app repo's docs/auth-emails.md.

Your fork predates the commit that added /auth/callback and /reset-password. Merge upstream main (see Keeping your fork up to date).

The default flow is PKCE: the browser that requested the email holds a one-time verifier cookie the callback needs. Opening the link on another device or browser fails the exchange. Email confirmation has still succeeded (just sign in); a password reset must be finished in the same browser it was requested from, or requested again there. To make links device-independent, switch the Supabase email templates to the token-hash form described in docs/auth-emails.md.

Supabase

"Could not load your permissions" banner, everything read-only, Invalid schema: public

The dashboard shows Your account role did not load, so every action is treated as read-only with the reason Invalid schema: public, and the server logs show PostgREST error PGRST002. Every REST request fails, so the app cannot load your role and fails closed — that banner is doing its job.

This is a PostgREST configuration problem on the Supabase side, not a WACRM bug and not a grants/RLS problem. The project's control-plane db_schema setting still lists a schema that no longer exists (one that was added to Exposed schemas and later dropped or renamed). The Dashboard hides stale names because it only lists schemas that exist, but PostgREST still tries to introspect them, fails with SQL state 3F000, and then rejects every request — including ones against public.

NOTIFY pgrst, 'reload schema', toggling schemas in the Dashboard, and Pause/Restore all leave the stale value in place. Fix it through the Management API:

  1. GET https://api.supabase.com/v1/projects/{ref}/postgrest — note what db_schema actually contains.
  2. PATCH https://api.supabase.com/v1/projects/{ref}/postgrest with only the schemas that exist, e.g. {"db_schema": "public,graphql_public"}.
  3. Restart the project from Settings → General (a restart, not Pause/Restore).

Background and diagnostic queries: https://github.com/orgs/supabase/discussions/45859. Originally reported in wacrm#526.

WhatsApp®

Webhook verification fails in Meta

  • The verify token in Meta must match exactly what you saved in Settings → WhatsApp inside the app.
  • The callback URL must be reachable without auth. Test it yourself: curl 'https://crm.example.com/api/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=<token>&hub.challenge=test' should return test.
  • META_APP_SECRET must be set on the deployment (it's required — without it, every inbound POST gets a 401). If it's set but wrong, you'll also see 401s for every POST while the initial subscribe handshake (which doesn't use it) works. Double-check the value matches Meta → App Settings → Basic → App Secret exactly.

Messages go out but nothing comes in

  1. Confirm the webhook is Subscribed to messages under Meta → WhatsApp → Configuration.

  2. Confirm the phone number is listed under Recipients during testing (test-number apps only deliver to whitelisted testers).

  3. Check server logs — inbound webhooks hit POST /api/whatsapp/webhook. A 200 with no message row means the payload parsed successfully but didn't match any known contact's config — double-check phone_number_id.

  4. If the log says Multiple configs (N) found for phone_number_id, two or more users on the instance have saved the same number. Every inbound message for it is being dropped. This shouldn't happen on 0.2.1+ (the API returns 409 and a DB constraint blocks duplicates), but a row created before that release can still trip it. To resolve:

    SELECT phone_number_id, array_agg(user_id) AS owners, count(*) AS n
    FROM whatsapp_config
    GROUP BY phone_number_id
    HAVING count(*) > 1;
    

    DELETE the row(s) for the user(s) that should not own the number, then apply supabase/migrations/013_whatsapp_config_phone_number_id_unique.sql if you haven't already. wacrm is single-tenant per WhatsApp number by design — see whatsapp-setup.md.

Token decryption failed

ENCRYPTION_KEY changed since the token was saved. Reconnect the WhatsApp account from Settings → WhatsApp to re-encrypt with the current key.

Automations

Wait steps never resume

The cron drain is not running. See automations-and-cron.md.

Quick sanity check from your machine:

curl -s -H "x-cron-secret: <secret>" https://crm.example.com/api/automations/cron

If that returns {"processed":N} with N growing when a wait is due, the endpoint is fine and the problem is just the schedule.

An automation fires twice for the same message

Most likely two app instances share one Supabase project and both receive the same webhook. The webhook handler deduplicates by Meta's wamid, but only within a single deploy — split-brain setups can double up. Either consolidate to one deploy or route the webhook to exactly one.

Deploy

502 Bad Gateway or "Application not running" from Hostinger

  • Open the Node.js app page in hPanel and confirm the status is Started.
  • Check the app log viewer for a crash on boot — almost always a missing env var. Re-save your env vars, re-run the build, and restart.
  • Confirm the Application startup command is npm start (or equivalent) and that a build has actually run (.next/ exists in the application root).

Env var changes don't take effect in the browser

NEXT_PUBLIC_* values are baked into the client bundle at build time. After changing any of them, re-run npm run build (hPanel's Run NPM Build button works) and restart the app.

Meta webhook suddenly stops after a domain change

Hostinger provisions a new SSL certificate when you attach a new domain or subdomain. Until it is issued, the webhook URL serves plain HTTP and Meta rejects it. Wait for AutoSSL to complete (minutes), then re-verify the webhook in Meta.

Still stuck?

Open an issue with:

  • Environment (local / Hostinger Managed Node.js / other).
  • The exact error from the app log viewer in hPanel or Next's dev output.
  • What you were trying to do when it happened.