Keys and environment variables

For whoever hosts Hela. Integrations work with nothing more than the database. Each OAuth partner requires that an application be registered with it: the operator may do so once for every company (keys below), but need not — a company can register its own from the connector's page, with its own client id and secret.

From the back office

Since 3.0, everything below — except INTEGRATIONS_KEY — can also be set in the back office → Integrations and keys, without a redeploy: the OAuth clients, the public addresses of the API and the app, the Apple sign-in keys. A value saved there wins over the environment variable of the same name; cleared, the variable stands in again. Secrets are sealed with INTEGRATIONS_KEY and never shown again. Instances re-read the settings every thirty seconds.

INTEGRATIONS_KEY deliberately stays in the environment: it is what seals everything else, and a lock whose key is kept in the same drawer is not a lock.

API variables

Variable Role
INTEGRATIONS_KEY The key that encrypts connection secrets (AES-256-GCM). A random string of 32 bytes or more; several keys separated by commas, newest first, to rotate without decrypting anything by hand. Without it, a key is derived from JWT_SECRET and a warning is logged at start-up.
API_PUBLIC_URL The API's public address (https://api.example.com/v1), used to build OAuth return addresses and partner notification addresses. Required for OAuth and incoming webhooks.
MIGRATE_ON_BOOT false to keep the API from applying the platform schema's migrations itself at start-up (see The database). It applies them by default.
WEB_PUBLIC_URL The app's public address, where the browser is sent after a Google, Microsoft or Apple sign-in. Defaults to the first origin of WEB_ORIGIN.
INTEGRATIONS_WORKER off to not run the job queue in this process (when another process does). By default every API instance processes the queue, with a database lock (SKIP LOCKED): several instances do not step on each other.
NODE_ENV In production, webhooks to localhost and private addresses are refused; in any other mode they are allowed, for development.

OAuth keys

An OAuth connector without its two variables is not unavailable: its page asks the company for its own application (client id and secret, with the callback address shown), and the connection goes through it. The variables below merely spare every company that step.

Partner Variables Where to create them Return address to declare
Google (Drive, Sheets) GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET Google Cloud Console → APIs & Services → Credentials → OAuth client (Web). Enable the Drive and Sheets APIs. Scopes requested: drive.file, spreadsheets. ${WEB_PUBLIC_URL}/integrations/callback/google-drive and …/google-sheets
Microsoft (OneDrive, Excel) MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET Entra admin center → App registrations. Type multitenant + personal accounts. Permissions Files.ReadWrite.AppFolder, offline_access. …/callback/onedrive, …/callback/excel-online
Dropbox DROPBOX_CLIENT_ID, DROPBOX_CLIENT_SECRET Dropbox App Console, App folder access. …/callback/dropbox
QuickBooks QUICKBOOKS_CLIENT_ID, QUICKBOOKS_CLIENT_SECRET Intuit Developer → app → Keys & credentials (production after review). Scope com.intuit.quickbooks.accounting. …/callback/quickbooks
Xero XERO_CLIENT_ID, XERO_CLIENT_SECRET developer.xero.com → My Apps → Web app. …/callback/xero
Yuki YUKI_CLIENT_ID, YUKI_CLIENT_SECRET Request to Yuki support (partner API). …/callback/yuki
Exact Online EXACT_CLIENT_ID, EXACT_CLIENT_SECRET apps.exactonline.com → Register app. …/callback/exact
Pennylane PENNYLANE_CLIENT_ID, PENNYLANE_CLIENT_SECRET Pennylane → Developers → Applications. …/callback/pennylane
DocuSign DOCUSIGN_CLIENT_ID, DOCUSIGN_CLIENT_SECRET DocuSign Admin → Apps and Keys (demo account to start). …/callback/docusign
Sage Business Cloud SAGE_CLIENT_ID, SAGE_CLIENT_SECRET developerselfservice.sageone.com → Create app. …/callback/sage
Zoho Books ZOHO_CLIENT_ID, ZOHO_CLIENT_SECRET api-console.zoho.com → Server-based application. …/callback/zoho-books
Google Calendar the same as Google, with the Calendar API enabled …/callback/google-calendar
Mollie Connect MOLLIE_CLIENT_ID, MOLLIE_CLIENT_SECRET Mollie dashboard → Developers → Applications. …/callback/mollie
Square SQUARE_CLIENT_ID, SQUARE_CLIENT_SECRET developer.squareup.com → application → OAuth (production). …/callback/square
SumUp SUMUP_CLIENT_ID, SUMUP_CLIENT_SECRET me.sumup.com → Developers → OAuth. …/callback/sumup
Slack SLACK_CLIENT_ID, SLACK_CLIENT_SECRET api.slack.com/apps → app → scopes incoming-webhook, chat:write. …/callback/slack
Stripe Connect STRIPE_CLIENT_ID, STRIPE_CLIENT_SECRET Stripe dashboard → Connect → Settings: the ca_… id; the secret is the platform's sk_live_… secret key. Without the pair, each company pastes its own Stripe key. …/callback/stripe
Apple (sign-in) APPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_PRIVATE_KEY developer.apple.com → Services ID + Sign in with Apple key. ${API_PUBLIC_URL}/auth/oauth/apple/callback
Google, Microsoft (sign-in) the same pairs as above …/auth/oauth/google/callback, …/auth/oauth/microsoft/callback

The callback address is on the web app's domain (WEB_PUBLIC_URL, by default the first origin in WEB_ORIGIN), for example https://hela-web.vercel.app/integrations/callback/xero: it is the one a company declares in the partner's console, and the connector's page shows it ready to copy. The app forwards it to the API, which does the exchange. GET /companies/{id}/integrations/meta/callback-url returns the template, :provider to be replaced by the connector key.

Key-based connectors (Odoo, Stripe, Mollie, PayPal, Square, SumUp, mobile money, Yousign, Clockify, Toggl, Shopify, WooCommerce, GoCardless, Ponto, Plaid, messaging, rates) need no variable: each company enters its own credentials.

The web app

Variable Role
NEXT_PUBLIC_API_URL The API address, the same as API_PUBLIC_URL. Without it the app is in demonstration mode and the Integrations, Developers and Compliance pages show the catalogue without connecting anything.

The database

Integration tables live in the platform schema (domain_events, integration_connections, integration_jobs, integration_links, integration_logs, api_keys, webhook_endpoints, webhook_deliveries, fiscal_regimes, fiscal_submissions, platform_settings, user_identities, login_codes) and in each company schema (fiscal_journal, time_entries, bank_transactions). Since 3.0.1 the API applies pending platform migrations itself at start-up, under a Postgres advisory lock (two instances starting together do not collide), before taking its first request; company schemas are brought up to date right after, as before. pnpm --filter api migration:run still works from a pipeline, and MIGRATE_ON_BOOT=false restores the old restraint. Every 3.0 migration is additive: no existing table or column is altered or dropped.

Retention: events and connection logs are kept 90 days, webhook deliveries 30 days, finished jobs 7 days. A clean-up runs every five minutes.

Network

The API must be able to reach out to partners (HTTPS) and administrations — some (eTIMS, VFD) on fixed IPs a corporate firewall must let through. Incoming webhooks and OAuth returns require API_PUBLIC_URL to be reachable from the Internet.

Outgoing webhooks refuse private addresses, localhost, and names resolving to them, at creation and on every delivery; redirects are not followed.

Checking

  • The start-up log warns if INTEGRATIONS_KEY is missing (key derived from JWT_SECRET).
  • A company's Integrations page shows To set up on every OAuth connector without an application — neither the platform nor the company registered one.
  • If that page says The server did not respond, the API is unreachable or its tables are behind: the start-up log's [Migrations] line says what was applied.
  • Creating a webhook to a https://webhook.site/… and pressing Test checks network egress and the job queue.

¿Algo incorrecto o ausente? Escríbanos.