ezacto developers

Integrations: where the keys go

ezacto talks to three money systems, and none of them share a setup path:

What it does Doorway Guide
QuickBooks Online mirrors every sent invoice and every payment into the books, and reads payments made there OAuth: an administrator presses Connect QuickBooks setup
BILL creates an opted-in client's invoices in BILL and closes them here when the client pays there a developer key and a login, no connect step BILL setup
Wise pays contractors from the organisation's Wise account an API token, no connect step Wise setup

Each guide says what to create at the vendor. This page is the part they share: how the resulting values reach a deployment, and how to prove they did.

The values

Every integration is configured by environment variables, all registered with their meaning in .dev.vars.example. The names are the same for the Worker and the container:

Integration Variables
QuickBooks QUICKBOOKS_CLIENT_ID, QUICKBOOKS_CLIENT_SECRET, QUICKBOOKS_WEBHOOK_VERIFIER_TOKEN, QUICKBOOKS_ENVIRONMENT
BILL BILL_DEV_KEY, BILL_COMPANY_ID, BILL_USERNAME, BILL_PASSWORD, optionally BILL_REPLY_TO_USER_ID and BILL_ENVIRONMENT
Wise WISE_TOKEN, optionally WISE_PROFILE_ID and WISE_WEBHOOK_PUBLIC_KEY

An integration whose values are absent is simply not there: its routes are not mounted, the settings page says it is not configured, and nothing else changes. There is no half-configured state that looks alive -- BILL with three of its four values refuses to start rather than pretending.

The Worker: GitHub environment secrets

The Cloudflare deployment is made by deploy.yml, and every secret it installs comes from the GitHub environment it deploys: dev for the development host and prod for the production one. The workflow converges the Worker's secrets to what the environment holds on every deploy, so a value put on the Worker by hand with wrangler secret put lasts until the next deploy and then reverts. Put values in the environment, never on the Worker directly.

The values live in a local, gitignored <vendor>.env file and go up with the GitHub CLI, one environment at a time. A dotenv-formatted file is accepted as is:

grep -E '^BILL_' bill.env | gh secret set -f - --env prod -R <owner>/<repo>

Two things to know about that command. It sets exactly the names in the file, so a value the vendor does not need is left out by leaving it out of the file. And a value that contains = -- Sentry's org tokens do -- must be read with cut -d= -f2-, not split on the first =, or the secret is silently truncated and the vendor answers invalid token on every deploy.

Keep an integration to the environments that should have it. BILL is usually production only; QuickBooks has a development key for the sandbox company and a production key for the real one, and they must not cross.

The container

The container reads the same names from its environment (docker run -e, a compose file, or a systemd unit). See Self-host in a container.

Deploying, and proving it

A merge to main deploys the development host only. Production is a manual dispatch of deploy.yml with environment=prod, and the deploy job's log names the Worker it uploaded. A value added to the prod environment is not on production until that dispatch has run.

After the deploy, each integration reports on itself without a browser:

Integration Route Configured looks like
QuickBooks GET /api/v1/integrations/quickbooks configured: true, and a connection naming the company and realm once an administrator has pressed Connect
BILL GET /api/v1/integrations/bill configured: true, the company id and environment
Wise GET /api/v1/integrations/wise configured: true, the paying profile's id and name, how many people it can pay, and whether webhooks can be verified

Settings → Integrations and Settings → Payouts show the same facts, with a Retry where a request failed and the request id the Worker log is searched by.

Webhooks

The three inbound endpoints are on the site root, not under /api/v1, and each verifies the vendor's signature before it reads anything:

https://<your-host>/webhooks/quickbooks
https://<your-host>/webhooks/wise
https://<your-host>/webhooks/stripe

Register a webhook at the vendor after the deployment carries its key: vendors start delivering as soon as the subscription is saved, and deliveries to an endpoint that is not mounted are retried and then dropped.