ml-connector
FreshBooksAdyen

FreshBooks and Adyen integration

FreshBooks keeps your invoices, clients, and books. Adyen moves the money. Connecting the two records what Adyen actually settles back against the matching FreshBooks invoice, so an invoice is marked paid the moment the payment clears instead of being reconciled by hand. Refunds and processing fees from Adyen settlement are written into FreshBooks too, so the books reflect the net cash received. ml-connector handles the very different APIs on each side and keeps invoice status in step with real payment activity on the cadence you set.

How FreshBooks works

FreshBooks exposes clients, invoices, payments, bills, bill vendors, expenses, journal entries, and chart of accounts through a REST API under an account-specific path. It authenticates with OAuth 2.0 Authorization Code grant only; there is no client-credentials option, so every token represents a consenting user and must be refreshed, with each refresh invalidating the prior refresh token. Almost every call needs an accountId or businessId obtained from the identity endpoint after OAuth. FreshBooks can push invoice and payment webhooks signed with HMAC-SHA256, but delivery latency is not guaranteed and is not suitable as a real-time trigger for payment-critical work, so polling is used as a backstop.

How Adyen works

Adyen is a payments platform, not an ERP, so it has no native invoice, client, vendor, or GL account objects. It exposes payments, captures, refunds, payouts and transfers, disputes, and reconciliation reports through versioned REST APIs, each with its own base URL. Every call carries an X-API-Key header or Basic auth scoped to a company or merchant account, and live endpoints require a merchant-specific URL prefix that has no generic fallback. Adyen pushes events such as AUTHORISATION, CAPTURE, REFUND, CHARGEBACK, and REPORT_AVAILABLE by webhook, each signed with HMAC-SHA256, and settlement reconciliation reports are the canonical source for fees and net settlement.

What moves between them

The flow runs from Adyen into FreshBooks. When Adyen captures a payment, ml-connector matches it to the FreshBooks invoice by the merchant reference Adyen carries and posts an invoice payment, which moves that invoice toward Paid. Adyen refunds are recorded against the same invoice or client, and processing fees and net settlement amounts taken from the reconciliation report are written into FreshBooks so the books match the cash that arrived. Capture, refund, and report-available webhooks drive the work as activity settles, and a scheduled poll backfills anything a webhook missed. Adyen is treated as a read-only accounting source, so ml-connector never writes payment instructions back into Adyen.

How ml-connector handles it

ml-connector stores both credential sets encrypted. On the FreshBooks side it runs the OAuth Authorization Code grant, refreshes the access token when a call returns 401, and rotates the stored refresh token on each refresh since FreshBooks invalidates the prior one. It resolves the accountId and businessId from the identity endpoint so it can build the correct account-scoped paths. On the Adyen side it sends the X-API-Key and uses the merchant live URL prefix on every live call. Adyen capture, refund, and report-available webhooks are HMAC-SHA256 verified before processing, and because Adyen can deliver the same notification more than once, the pspReference is used to dedupe so a payment is never recorded twice. Matching is done on Adyen merchantReference against the FreshBooks invoice number, which is why supplying your own invoice number keeps creates and payment posts idempotent given FreshBooks has no idempotency-key header. Fees and net amounts come from the reconciliation report rather than the raw authorisation, since the report is the canonical settlement record. FreshBooks invoices created by API start as Draft and are not in reports until marked Sent, so the connector only posts payments against sent invoices. Adyen rate limits and FreshBooks 429 short-burst throttling both trigger exponential backoff, and every record carries a full audit trail and can be replayed if a downstream call fails.

A real-world example

A twelve-person design studio bills retainer and project work through FreshBooks and takes card payment through Adyen on a hosted checkout. Before the integration, a bookkeeper exported the Adyen payments list every week and manually marked the matching FreshBooks invoices as paid, then reconciled processing fees by hand at month end, which meant invoices sat open for days and the cash in the books rarely matched the bank. With FreshBooks and Adyen connected, each Adyen capture marks its invoice paid within minutes, refunds reverse cleanly against the original invoice, and the weekly reconciliation report posts the net settlement and fees automatically. The owner sees a true paid-versus-outstanding picture and the manual matching step is gone.

What you can do

  • Record Adyen captured payments against the matching FreshBooks invoice so it moves to Paid without manual entry.
  • Match payments by Adyen merchant reference to the FreshBooks invoice number and dedupe on pspReference.
  • Post Adyen refunds and processing fees from the reconciliation report into FreshBooks so the books show net cash.
  • Bridge the Adyen API key and live URL prefix with FreshBooks OAuth token and refresh-token rotation.
  • Run on Adyen capture, refund, and report webhooks with a scheduled poll backfill, retries, and a full audit trail.

Questions

Which direction does data move between FreshBooks and Adyen?
Data moves from Adyen into FreshBooks. Settled payments, refunds, and fees flow from Adyen and are recorded against the matching FreshBooks invoices and accounts. Adyen has no invoice or GL objects and is treated as a read-only accounting source, so ml-connector never writes payment instructions back into Adyen.
How does ml-connector match an Adyen payment to a FreshBooks invoice?
It matches on the merchant reference Adyen carries against the FreshBooks invoice number. Because FreshBooks has no idempotency-key header, supplying your own invoice number keeps the match reliable, and the Adyen pspReference is used to dedupe so a duplicated webhook never records the same payment twice.
Why does the integration use Adyen reconciliation reports instead of just the payment webhooks?
The webhooks tell ml-connector that a capture or refund happened, but the reconciliation report is Adyen's canonical record of net settlement and processing fees. ml-connector posts the invoice payment from the capture event and then uses the report-available webhook to write fees and net amounts, so the FreshBooks books reflect the actual cash received rather than the gross authorised amount.

Related integrations

Connect FreshBooks and Adyen

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started