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_KEYis missing (key derived fromJWT_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.