Wave Accounting and Avalara integration
Wave Accounting runs invoicing, customers, and accounting for small businesses through a GraphQL API. Avalara AvaTax determines the correct sales tax for every jurisdiction. Connecting the two means each Wave invoice is sent to AvaTax for an accurate tax figure that is applied back onto the invoice, so the tax a customer sees is never a manual estimate. ml-connector handles the very different APIs on each side and moves the data on a schedule you control. Because AvaTax holds no chart of accounts, the books stay in Wave Accounting where they belong.
What moves between them
The main flow runs from Wave Accounting into Avalara. ml-connector reads Wave invoices and posts each one to AvaTax as a SalesInvoice transaction, sending the customer address, line items, and amounts for jurisdictional tax calculation. The calculated tax comes back synchronously and ml-connector applies it to the Wave invoice as a Wave sales tax object on the matching line items. Wave customers flow the same direction as AvaTax customers so exemption certificates resolve correctly, and Wave products align with AvaTax tax codes so each line is classified. A Wave invoice that is deleted is sent to AvaTax as a void or ReturnInvoice so the tax record stays in step. Wave has no AP bills or purchase orders, so only customer sales tax is calculated, and AvaTax holds no ledger, so no accounting records ever flow back into Wave.
How ml-connector handles it
ml-connector stores both credential sets encrypted. On the Wave side it runs the OAuth 2.0 Authorization Code grant with the offline_access scope, then proactively refreshes the roughly two-hour access token before it expires rather than waiting for a failure mid-run, and it fetches the business ID once from the businesses query because every later call is scoped to it. On the Avalara side it sends the Base64 account-ID-and-license-key Basic auth header on each request and selects the sandbox or production base URL to match the credentials, since sandbox keys are rejected in production. Wave invoices have no patch mutation and move through DRAFT, SAVED, SENT, and PAID states, so ml-connector tracks status and submits the transaction once an invoice is approved rather than while it is a draft. It uses the Wave invoice number as the AvaTax document code: an uncommitted code updates in place on retry, and CreateOrAdjustTransaction keeps the post idempotent so a re-read invoice is never double-taxed. Wave pushes HMAC-signed webhooks for invoice events, which serve as the trigger, but because GraphQL errors arrive with HTTP 200 the response body errors array is always checked, and a scheduled poll using page and pageSize backfills anything a webhook missed. Customer addresses are validated through the AvaTax resolve call before calculation, since a bad address fails the tax lookup. Both sides return HTTP 429 under throttling, so ml-connector backs off with jitter and retries, and every record carries a full audit trail and can be replayed if a downstream call fails.
A real-world example
A six-person online retailer sells handmade goods to customers across several US states and runs its books on Wave Accounting. Before the integration, the owner looked up sales tax rates by hand for each customer's state and typed them onto invoices, which produced wrong tax on orders shipped to newer jurisdictions and a growing worry about what was actually owed at filing time. With Wave Accounting and Avalara connected, each invoice is sent to AvaTax for the exact rate at the customer's address and the figure is applied back before the invoice is sent, while every committed transaction is recorded for Avalara to report and file. The manual rate lookups are gone and the retailer enters tax season with numbers that match what was billed line for line.
What you can do
- Calculate jurisdiction-accurate sales tax on Wave Accounting invoices through AvaTax and apply the result back to the invoice.
- Map Wave customers to AvaTax customers and Wave products to AvaTax tax codes so exemptions and taxability resolve.
- Use the Wave invoice number as the AvaTax document code so a retried submission never creates a duplicate transaction.
- Bridge Wave OAuth 2.0 refresh tokens and the Avalara account-and-license-key Basic auth header across every call.
- Trigger on Wave HMAC-signed webhooks and backfill by polling, with address validation, retries, and a full audit trail.
Questions
- Which direction does data move between Wave Accounting and Avalara?
- The main flow is Wave into Avalara. Invoices move from Wave Accounting to AvaTax for tax calculation, and the calculated tax is applied back onto the Wave invoice as a sales tax object. Wave customers and products are also mapped into AvaTax customers and tax codes so each line is classified. AvaTax has no chart of accounts of its own, so no accounting records ever flow back into Wave.
- How does the integration avoid taxing the same Wave invoice twice?
- ml-connector uses the Wave invoice number as the AvaTax document code, which AvaTax treats as its idempotency key. An uncommitted transaction with the same code updates in place, and using CreateOrAdjustTransaction makes the post idempotent regardless of commit state. A Wave invoice that is re-read during a poll therefore updates its existing AvaTax transaction rather than creating a duplicate.
- Does Wave push invoices, or does ml-connector poll for them?
- Both. Wave pushes webhooks for invoice events, signed with HMAC-SHA256 in an x-wave-signature header, and ml-connector verifies the signature and uses the event as a trigger. Because a webhook can be missed, it also reconciles by polling the GraphQL API on a schedule with page and pageSize, and it always checks the errors array in the response body since Wave returns GraphQL errors with HTTP 200.
Related integrations
More Wave Accounting integrations
Other systems that connect to Avalara
Connect Wave Accounting and Avalara
Free to use. Add your credentials, ping your real systems, and see if we fit.
Get started