Freshh for Craft CMS

Troubleshooting

Start at the log

Freshh → Log holds every call with its payload, status code and duration. freshh/log/errors prints the last twenty failures from the console. Most questions below are answered there in one line.

"The invoice would total X but the order came to Y. Nothing was sent."

Freshh does FreshBooks' arithmetic before sending and refuses to send an invoice that disagrees with what the customer paid. The mapping notices on the preview say which adjustment could not be expressed. The usual causes:

  • A tax with no rate and no taxable base, so no percentage could be derived. Give the tax rate a real rate in Commerce, or express the charge as a fee rather than a tax.
  • An order-level tax on the order total. FreshBooks has nowhere to put it, so it becomes a charge line — the total is right but the tax report will not count it.
  • More than two taxes on one line. They are combined, which is exact; if the invoice still does not balance, something else is wrong.

Raising Largest rounding difference is not the fix. It exists for cents, and a larger gap is a mapping problem you want to see.

An order never reached FreshBooks

In order of likelihood:

  1. The queue is not running. Freshh queues everything. Check Craft's queue.
  2. The order is not eligible — syncing is off, the trigger status never fired, or a store filter excludes it. freshh/sync/status counts unsent orders.
  3. The order is excluded. Check the link's state on Freshh → Invoices; a skipped order says so.
  4. It failed. The link row keeps the last error, and the queue keeps the failed job. freshh/sync/retry re-runs everything that failed.

"Order … has no email address"

FreshBooks will not create a client without one, and an invoice needs a client. An order with no email cannot be invoiced — that is FreshBooks' rule, not Freshh's.

Draft invoices, or an empty revenue report

A FreshBooks invoice left in draft does not appear in accounting reports at all. If a whole store of invoices is invisible, check that Mark invoices as sent is on. Existing drafts can be marked sent in FreshBooks, or re-sent from Freshh with Re-send.

The connection keeps dropping

FreshBooks refresh tokens are one-time-use, and only one is valid per user per application at a time. Anything else refreshing the same app's tokens — a second site, a script, another integration sharing the client ID — will invalidate Freshh's, and vice versa. Give each site its own FreshBooks app.

Do not run freshh/connection/refresh on a schedule either. Freshh refreshes on demand, under a mutex; a cron job refreshing every minute just spends tokens.

If the settings screen says the site must be reconnected, FreshBooks has forgotten the refresh token and only reconnecting will help.

Callbacks never verify

FreshBooks posts a verification code to the webhook URL and expects an answer within ten seconds. It cannot do that if the site is not publicly reachable — local development, staging behind basic auth, an IP allowlist. Use freshh/sync/pull on a schedule instead.

freshh/webhooks/list shows what FreshBooks has registered and whether each is verified.

Callbacks arrive but nothing happens

The endpoint answers 403 unverified when the signature does not match. That is almost always a webhook registered against a different site — a cloned database still holding the original site's callback records. freshh/webhooks/forget clears the local records without touching FreshBooks, then register again.

Duplicate invoices

You should not get any: Freshh searches FreshBooks for the invoice number before creating one. The two ways to defeat that are changing the invoice number source after orders have synced, or deleting link rows and re-syncing with a different numbering scheme. Both make the old invoice unfindable under the new number.

"FreshBooks did not respond"

A timeout or a DNS failure. Freshh retries twice with backoff and then fails the job. Retry it once FreshBooks is up; the reconcile step means a retry cannot duplicate anything.

Rate limits

FreshBooks throttles without publishing numbers or headers. Freshh backs off exponentially on 429 and 5xx. A large backfill is better run with --limit in batches than all at once.