Calculate sales tax, GST and VAT with Avalara

Quote sales tax, GST, HST, QST and VAT in Avalara AvaTax, add the tax to Wingspan invoices as line items, and record, void and refund it in AvaTax as you send, cancel and refund invoices.

Your integration calls AvaTax before it creates each Wingspan invoice, adds the tax as invoice lines, then keeps the AvaTax transaction in step as the invoice changes. Wingspan doesn't call Avalara for you. Sales tax, GST and VAT on invoices covers the pattern and who owns each step. The example quotes GST and QST on a Canadian invoice. US sales tax uses the same calls, and Avalara lists VAT and GST calculation for 190+ countries.

sequenceDiagram
  participant You as Your integration
  participant AT as AvaTax
  participant WS as Wingspan
  You->>AT: Quote tax (SalesOrder)
  AT-->>You: totalTax and one row per tax
  You->>WS: Create the invoice with tax lines
  You->>WS: Send the invoice
  WS-->>You: Invoice.Opened
  You->>AT: Record the tax (SalesInvoice, committed)
  WS-->>You: Invoice.Cancelled or Invoice.RefundCompleted
  You->>AT: Void or refund the transaction

Before you start

  • An AvaTax account ID and license key. Ask Avalara for a sandbox account to build against, because its free trial accounts are production accounts.
  • Your company set up in AvaTax, with its company code and every state and province where you collect tax.
  • The Avalara tax code for each kind of service you invoice. List the codes with GET /api/v2/definitions/taxcodes, and confirm your choices with Avalara or your tax advisor.
  • A Wingspan credential that can create, send and refund invoices, and a webhook subscription for Invoice.Opened, Invoice.Cancelled and Invoice.RefundCompleted.
AvaTax environmentBase URL
Sandboxhttps://sandbox-rest.avatax.com
Productionhttps://rest.avatax.com

AvaTax uses basic authentication. Send Authorization: Basic with your account ID and license key, joined by a colon and Base64-encoded. GET /api/v2/utilities/ping returns "authenticated": true when the credentials work.

Step 1. Quote the tax

Before you create the invoice, ask AvaTax for the tax with POST /api/v2/transactions/create. A SalesOrder is a quote that AvaTax doesn't record, so you can quote as often as the draft changes.

curl -X POST "https://sandbox-rest.avatax.com/api/v2/transactions/create?\$include=Summary" \
  -H "Authorization: Basic $AVATAX_AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "SalesOrder",
    "companyCode": "DEFAULT",
    "code": "inv-2026-10-1042",
    "date": "2026-10-12",
    "customerCode": "Pq8Lx2Nv5Rw9Tk3Hm6Bz1C",
    "currencyCode": "CAD",
    "addresses": {
      "shipFrom": { "line1": "100 King St W", "city": "Toronto", "region": "ON", "country": "CA", "postalCode": "M5X 1A9" },
      "shipTo": { "line1": "1 Place Ville Marie", "city": "Montreal", "region": "QC", "country": "CA", "postalCode": "H3B 2C4" }
    },
    "lines": [
      { "number": "1", "quantity": 1, "amount": 1000.00, "taxCode": "<your tax code>", "description": "Clinical staffing, 8 hours" }
    ]
  }'
FieldWhat to send
codeYour reference for this invoice. Use the same value as the Wingspan invoice's externalId. Avoid spaces and / + ? # %.
dateThe invoice date. AvaTax applies the rates in force on that date.
customerCodeThe Wingspan payer ID, so AvaTax can match the client's exemption certificates. It's case sensitive.
addresses.shipToWhere the service was delivered. AvaTax takes the state or province from this address. For staff working at a client's site, send the site's address.
addresses.shipFromWhere your business supplies the service from.
linesOne line for each service line on the invoice, with its amount and taxCode.
currencyCodeThe invoice currency. Use CAD for Canadian invoices.

The response carries the total tax and one summary row for each tax and jurisdiction.

{
  "code": "inv-2026-10-1042",
  "status": "Temporary",
  "totalAmount": 1000.00,
  "totalTaxable": 1000.00,
  "totalTax": 149.75,
  "summary": [
    { "country": "CA", "rate": 0.05, "taxable": 1000.00, "tax": 50.00 },
    { "country": "CA", "region": "QC", "rate": 0.09975, "taxable": 1000.00, "tax": 99.75 }
  ]
}

Each row also carries taxName, taxType, jurisType and jurisName. Put taxName and rate in the line description your client sees, and key any logic on country, region, jurisType and taxType, because Avalara treats taxName as display text.

Step 2. Create the invoice with the tax lines

Create the invoice with Create an invoice. Add one Custom line for each tax after the service lines, and set externalId to the AvaTax code.

curl -X POST https://api.wingspan.app/v3/payments/invoices \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "X-Wingspan-Account: $WINGSPAN_ACCOUNT_ID" \
  -H "Idempotency-Key: inv-2026-10-1042-create" \
  -H "Content-Type: application/json" \
  -d '{
    "payerId": "Pq8Lx2Nv5Rw9Tk3Hm6Bz1C",
    "externalId": "inv-2026-10-1042",
    "currency": "CAD",
    "dueDate": "2026-11-11",
    "lineItems": [
      { "description": "Clinical staffing, 8 hours", "lineItemType": "Services", "totalCost": 1000.00 },
      { "description": "GST (5%)", "lineItemType": "Custom", "totalCost": 50.00, "accounting": { "taxCode": "GST" } },
      { "description": "QST (9.975%)", "lineItemType": "Custom", "totalCost": 99.75, "accounting": { "taxCode": "QST" } }
    ]
  }'

accounting.taxCode is your own code for each tax. Wingspan stores it as you send it, so your accounting system can post the line to the right liability account.

Step 3. Send the invoice and record the tax

Send the invoice with Send an invoice. Wingspan sends Invoice.Opened when it issues the invoice. When your webhook handler receives it, read the invoice with Get an invoice using the event's data.id, take its externalId as the AvaTax code, and record the tax with POST /api/v2/transactions/createoradjust. Send the quote body inside createTransactionModel, with type set to SalesInvoice and commit set to true.

{
  "adjustmentReason": "PriceAdjusted",
  "createTransactionModel": {
    "type": "SalesInvoice",
    "commit": true,
    "companyCode": "DEFAULT",
    "code": "inv-2026-10-1042",
    "date": "2026-10-12",
    "customerCode": "Pq8Lx2Nv5Rw9Tk3Hm6Bz1C",
    "currencyCode": "CAD",
    "addresses": {
      "shipFrom": { "line1": "100 King St W", "city": "Toronto", "region": "ON", "country": "CA", "postalCode": "M5X 1A9" },
      "shipTo": { "line1": "1 Place Ville Marie", "city": "Montreal", "region": "QC", "country": "CA", "postalCode": "H3B 2C4" }
    },
    "lines": [
      { "number": "1", "quantity": 1, "amount": 1000.00, "taxCode": "<your tax code>", "description": "Clinical staffing, 8 hours" }
    ]
  }
}

createoradjust creates the transaction, or adjusts it if one with the same code already exists, so it's safe to retry. AvaTax has no idempotency key header, and a plain create fails when a committed transaction with that code already exists. Invoice.Opened can also fire again if the invoice reopens. Sending createoradjust again with the same lines adjusts the transaction to the same values, so the tax isn't recorded twice.

AvaTax marks the transaction Committed. Avalara Managed Returns reports only committed transactions. If you change the invoice after sending it, quote again, update the tax lines with Update an invoice, and send createoradjust again with the new lines.

Step 4. Void the tax when you cancel the invoice

When you void the invoice in Wingspan and receive Invoice.Cancelled, void the AvaTax transaction.

curl -X POST "https://sandbox-rest.avatax.com/api/v2/companies/DEFAULT/transactions/inv-2026-10-1042/void" \
  -H "Authorization: Basic $AVATAX_AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "code": "DocVoided" }'

Once AvaTax reports a transaction on a filed return, it locks the transaction, and you can't void, adjust or uncommit it. Record a refund for the full amount instead.

Step 5. Record refunds and credits

When you refund an invoice and receive Invoice.RefundCompleted, record the refund with POST /api/v2/companies/{companyCode}/transactions/{code}/refund, where {code} is the original sale. The event carries the refund's ID in context.refundId. Read the invoice and find that refund in refunds[] to get its amount. AvaTax creates a return that reverses exactly the tax it calculated on the sale. For Managed Returns, refundDate decides which filing month carries the refund.

For a full refund, send the refund date and a code for the return.

{
  "refundTransactionCode": "inv-2026-10-1042-r1",
  "refundDate": "2026-10-20",
  "refundType": "Full"
}

For a partial refund, send the refunded share of the invoice total as a percentage. A refund of 229.95 on the 1,149.75 invoice is 20%.

{
  "refundTransactionCode": "inv-2026-10-1042-r2",
  "refundDate": "2026-10-20",
  "refundType": "Percentage",
  "refundPercentage": 20
}

To refund specific lines instead, send "refundType": "Partial" with their numbers in refundLines. These are the AvaTax line numbers you sent, which cover the service lines, and AvaTax refunds the tax on them.

For a credit that includes tax, get the tax from AvaTax before you create the credit.

  1. Create a ReturnInvoice with POST /api/v2/transactions/create. Send the same customerCode, addresses and taxCode as the sale, its own code such as inv-2026-10-1042-c1, commit set to false, and the credited service amount as a negative line amount.
  2. Create the Wingspan credit. On each line, set taxAmount to the tax AvaTax returned, as a positive number, and include it in the line's amount. A credit for 200.00 of service with 29.95 of GST and QST has an amount of 229.95 and a taxAmount of 29.95.
  3. Commit the return with POST /api/v2/companies/DEFAULT/transactions/inv-2026-10-1042-c1/commit?documentType=ReturnInvoice and { "commit": true }. Without documentType, AvaTax looks for a SalesInvoice with that code.

Exemptions

Keep each client's exemption certificates in AvaTax under the same customerCode you send on transactions. AvaTax checks for a matching certificate first, then exemptionNo, then entityUseCode. For an exempt client, the response shows the exempt amount in totalExempt, and you add no tax lines to the invoice.

US invoices

  • The taxCode on each line decides whether the service is taxable in the state where you delivered it. Confirm the code for staffing services with Avalara.
  • Washington taxes temporary staffing services from October 1, 2025, and AvaTax has a parameter for service contracts that predate that change. Ask Avalara which parameter to send. See Avalara's note on the Washington change.

Canadian invoices

  • AvaTax returns federal GST and provincial QST or PST as separate rows, so add a line for each. In HST provinces it returns HST as one combined rate or as federal and provincial parts, depending on your Avalara setting.
  • Invoice in CAD and send currencyCode CAD. Canadian returns use CAD, and AvaTax doesn't convert other currencies for them.
  • Confirm with Avalara, in writing, which of your Canadian returns Managed Returns files.

Related pages


Did this page help you?