Integrate an external tax service

Ask about this Page
Copy for LLM
View as Markdown

Learn how to calculate tax on Carts with an external tax service and record the resulting Orders for reporting and filing.

Learn more about integrations in our self-paced Integration patterns module.

Use an external tax service when your tax engine must calculate jurisdiction-specific amounts or apply product classifications and exemptions. You can also use it to record transactions for reporting and filing. A complete integration performs two separate jobs:

  • It calculates and applies tax synchronously whenever a tax-relevant Cart input changes.
  • It records the finalized Order and later voids or refunds the transaction asynchronously.
This guide covers calculation through a backend-for-frontend (BFF) or an API Extension. Both approaches use an asynchronous Order synchronizer for recording. Before continuing, obtain a commercetools Project, access to Connect, and test credentials for your tax service.

Define the tax requirements

Document the following decisions before selecting or configuring a Connector:

  • Tax engine: identify the engine, supported countries and currencies, account environment, credentials, and required API capabilities.
  • Nexus: list the countries, states, or other jurisdictions where you must collect tax. A destination outside the configured nexus can correctly produce zero tax.
  • Fulfillment and origin: identify delivery, pickup, drop-shipment, and multi-origin flows. Define which origin address applies to each shipment and where the calculator obtains it.
  • Product classification: define where each engine-specific tax code comes from and how products without a code are handled.
  • Customer exemptions: define which buyers qualify, which identifier or certificate reference the engine expects, and which system owns that data.
  • Calculation and recording: decide whether you need only a checkout quote or both a quote and a recorded transaction. Most filing workflows need both.
  • Order lifecycle: map the project-specific events or States that commit, void, or refund a provider transaction. Treat these as configuration rather than hardcoded State keys, because they differ between Projects. Include partial returns and Order Edits when they are supported by your process.
  • Failure policy: decide whether tax-engine failures block Cart updates and Order creation or allow browsing to continue temporarily. Define how the integration recovers before an Order can be created.
Also confirm whether prices include tax, which TaxCalculationMode applies, which RoundingMode the Cart taxRoundingMode uses, and the acceptable latency for tax-relevant Cart updates. LineItemLevel calculates tax after multiplying the item price by the quantity, while UnitPriceLevel calculates it per unit, so the mode changes the resulting amounts. These decisions affect the TaxMode and the integration failure policy. Tax-inclusive pricing in particular constrains which tax mode you can use.

Choose a Connector path

Check the live Connector catalog at the start of the implementation. Use the Search Connectors endpoint and the tax engine documentation to verify that a candidate is a deployable Connect Connector. Record the Connector key and version that you evaluated. A marketplace listing alone does not prove that an integration is deployable through Connect or that it supports your required lifecycle.

Coverage differs between tax engines, and that difference changes the effort estimate. Some engines have a certified public Connector that you can install and configure. Others have none, which makes the integration a build rather than an installation. Source availability also varies. Some Connectors are open source and can be forked, while others are partner-private and cannot. Determine which case applies to your engine from the catalog search above. Record the result together with the date you checked it.

Choose the first path that satisfies the requirements:

  1. Configure an existing deployable Connector when its calculation, recording, lifecycle, and regional capabilities fit.
  2. Prove whether configuration closes a gap before forking. Most apparent capability gaps are settings. Check the tax-code source, exemption handling, whether returns file a refund, address validation, and the States that commit or void a provider transaction. Certified Connectors commonly expose these as Merchant Center settings rather than in code. Check the configuration surface before concluding that you must change the source.
  3. Fork an existing Connector for a genuine gap when its source is available and configuration cannot provide the required behavior. Keep the customization limited to the missing capability.
  4. Build from a template when no existing Connector fits. The tax integration template supplies calculator and Order-sync application skeletons, but it is not a production-ready implementation. Implement and test the tax-engine mapping, endpoint authentication, least-privilege scopes, and lifecycle registration before production use. Verify that postDeploy registers the API Extension at the calculator endpoint and the Subscription at the synchronizer endpoint. A script that only installs dependencies does not register these resources.

Configure the Connector and data model

Configure the selected Connector from the approved requirements. Store tax-service credentials as secured configuration, and keep non-secret behavior settings in standard configuration. Use deployment scripts to register and remove the API Extension and Subscription with the Connector lifecycle.

Product data model

Products can have different tax treatment based on their type, intended use, or other characteristics. For example, clothing can be exempt in one jurisdiction while electronics use a standard rate, and luxury products can have an additional tax.

Use a Product Attribute for the engine tax code when the code differs between Product Variants. The selected Product Variant and its Attributes are included in the Cart Line Item, so the calculator can map the code without another Product lookup.
When a PIM or ERP owns the tax code, include it in the attribute ownership and synchronization contract described in Integrate product data.
Tax Categories can represent a shared classification at the Product level. However, the Product taxCategory Reference is not part of a Cart LineItem. You cannot expand it from that Line Item. If Tax Categories are the source, resolve the Product and Tax Category deliberately. Cache or precompute the engine-code mapping to avoid adding work to every synchronous calculation.

Define a fallback for missing and invalid codes. Depending on your compliance requirements, the fallback can use an engine default or block the calculation for correction.

Customer profile

Use Custom Fields on the Customer to store the exemption status and the identifier that the tax service requires. This value can be an exemption certificate reference or an engine-managed Customer identifier.
If a CRM owns the exemption data, use the ownership and conflict rules in Integrate a CRM before exposing the required value to the calculator.

Make the required exemption data available to the calculator without storing certificate documents or tax-service credentials on the Cart. For example, a BFF can copy the required non-sensitive identifier to a Cart Custom Field. Alternatively, the calculation service can retrieve the Customer with a bounded and cached lookup. Verify an exempt and a non-exempt Customer separately.

Tax mode

Use ExternalAmount when the tax service is authoritative for exact tax amounts. This mode avoids recalculating the provider amounts from rates, which can create rounding differences. With ExternalAmount, the Cart needs complete external tax data before it can be ordered. Set the Cart total tax and external tax amounts and rates on every Line Item, Custom Line Item, and Shipping Method.
In this mode the Cart taxedShippingPrice field is always empty. Avoid tax-inclusive pricing, because commercetools automatically sets taxedItemPrice.totalNet to the Line Item totalPrice.
Use External only when the integration supplies rates and commercetools should calculate the amounts. Tax-inclusive pricing is one reason to choose it. In this mode, set a rate instead of an amount on every priced element. Use Set LineItem TaxRate, Set CustomLineItem TaxRate, and Set ShippingMethod TaxRate. commercetools then calculates totalNet, totalGross, and taxPortions using the Cart taxRoundingMode, so the resulting amounts can differ from the tax-service quote.
Review the canonical TaxMode behavior for action ordering and price-affecting changes before implementing either mode.

Implement calculation and recording

Calculation and recording have different runtime requirements. Calculation is synchronous because the shopper needs a taxed Cart. Recording is asynchronous because tax reporting and filing must not add another external dependency to Order creation.

If your tax service also offers address validation, call it from the storefront or BFF during address entry rather than from the calculator. A validated destination improves the accuracy of the quote without adding another external call to every Cart operation.

BFF pattern

Use a BFF when every tax-relevant Cart operation passes through the same backend and you need direct control over when calculation occurs. The BFF first applies the tax-relevant change to the Cart. It then builds a quote request from the returned Cart, applies the external amounts in a second Cart update, and returns the taxed Cart. Prevent clients from bypassing this flow and creating an Order with missing or stale tax.

Build the quote from the Cart returned by the first update, not from the storefront request or an earlier Cart representation. Apply the tax actions using the returned Cart version. The BFF must follow the same ExternalAmount action sequence as the API Extension. Repeat the calculation after any tax-relevant change. These changes include an origin or destination address, fulfillment method, price, discount, quantity, shipping selection, product classification, or exemption.

API Extension pattern

Use an API Extension when tax must be applied consistently regardless of which client changes the Cart. The Extension invokes the calculator before commercetools persists the change. Its latency and availability therefore become part of every matching Cart operation.

Complete the calculator contract

Implement the calculator in this order:

  1. Register a Cart Extension for create and update operations. Add a conditional trigger so the service runs only when the Cart can be taxed. Require a destination, priced items, and the shipping data your engine needs.
  2. Validate and map the taxable elements of the Cart into one tax-service quote request. These are Line Items, Custom Line Items, shipping, discounts, product tax codes, exemption data, and origin and destination addresses.
  3. Skip the external request when no tax-relevant value changed and the Cart already contains a complete, current tax result. A hash of the tax inputs stored in a Cart Custom Field is one way to detect an unchanged calculation.
  4. Map the quote to Set LineItem TaxAmount, Set CustomLineItem TaxAmount, Set ShippingMethod TaxAmount, and Set Cart Total Tax actions. Return actions only for elements present on the Cart, but do not omit a priced element. Omitting one is not visible on the Cart. It surfaces later as a failure to create the Order, reporting that the element is missing an external tax amount and rate. Custom Line Items and shipping are the elements most often missed. For a Cart with Multiple ShippingMode, set shippingKey on every Set ShippingMethod TaxAmount action. Omitting it there returns an InvalidOperation error, as does supplying it for a Cart with Single ShippingMode.
  5. Follow the update ordering documented for TaxMode. Price-affecting changes can clear existing tax data and can require a separate request before tax amounts and the Cart total are set again.
  6. Return 200 or 201 for a successful HTTP Extension response. A 202 response is not a successful API Extension response and causes the Cart operation to fail.
API Extensions have a default timeout of 2 seconds. You can configure a timeout per Extension up to a maximum of 10 seconds. A longer limit requires a per-Project increase that is subject to a performance review. Confirm that your tax service answers within that ceiling, and set the tax-service timeout below the configured Extension timeout.

For a fail-closed policy, reject the operation when a complete and trustworthy calculation is unavailable. For a fail-open policy, return no tax actions only for operations that your compliance policy permits. Prevent Order creation until a fresh calculation supplies complete tax amounts.

Conditional triggers reduce calls before the service runs. The tax-input comparison reduces calls after it runs. Monitor both paths so that an unchanged Cart avoids another quote while every tax-relevant change causes recalculation.

Sequence the calculator against other Extensions

commercetools triggers all API Extensions registered for the same resource and action concurrently. If another Extension changes a tax input, such as a discount or a price, the calculator can run against the earlier Cart state. It then returns amounts that do not match the persisted result.

Declare the calculator's dependencies on those Extensions so that it waits for them to finish and receives the Cart state they produced. See Extension Chaining for the dependency model, the maximum number of direct dependencies, and the rejection of circular dependencies. Register these dependencies in the deployment scripts alongside the Extension itself.

Synchronize the Order lifecycle

Use a Connect event application with a Subscription for recording. The synchronizer must separate a checkout quote from the provider transaction used for reporting or filing.

For each relevant Message, complete the following sequence:

  1. Validate the delivery and Message type. Begin with OrderCreatedMessage, then add the project-specific State and return Messages required by the approved lifecycle.
  2. Fetch the current Order by resource.id instead of treating the delivery payload as the current resource.
  3. Map the Order to the tax-service recording API. Use a stable business identifier derived from the Order ID so that repeated processing does not create another provider transaction.
  4. Commit the transaction at the configured lifecycle point. Void it when the configured cancellation transition occurs, and send a full or partial refund when the corresponding return or refund signal occurs. If your process uses Order Edits, recalculate the recorded transaction when an edit changes a taxable amount.
  5. Acknowledge the delivery only after the provider operation succeeds, is already complete, or a permanent failure has been recorded for operator action. Return a failure for transient errors that should be redelivered.
Subscriptions provide at-least-once delivery and do not guarantee delivery order. Use the Message resource.id and sequenceNumber to identify deliveries, compare the current Order state before acting, and make commit, void, and refund operations idempotent. A duplicate Message must result in the same provider state without a second financial or tax effect.
Define recovery before release. commercetools retries an unacknowledged notification for up to 48 hours while the Subscription reports a TemporaryError, and can drop it after that. A ConfigurationError stops delivery after 24 hours for production Projects, or 1 hour for development and staging Projects. Monitor Subscription health, retain enough structured data to diagnose a failed provider call, and provide an idempotent replay procedure. Run a scheduled reconciliation that compares Orders eligible for recording with provider transactions so that dropped or permanently failed deliveries can be repaired.

Test the applications

Run automated tests without a deployed Connector or tax-service credentials. Keep the Cart-to-quote and quote-to-action mappings separate from network calls so that the tests can assert the complete result.

Test the following calculator behavior:

  • The response contains setLineItemTaxAmount, setCustomLineItemTaxAmount, setShippingMethodTaxAmount, and setCartTotalTax actions for every applicable element.
  • Money conversion, tax rates, both origin and destination addresses, and the shippingKey behavior for Single and Multiple ShippingMode Carts are correct.
  • Successful responses use 200 or 201, and unchanged tax inputs return no actions without another tax-service call.
  • Tax-service errors follow the approved fail-open or fail-closed policy.

Test the following synchronizer and deployment behavior:

  • The handler validates the delivery envelope and Message type, acknowledges irrelevant deliveries, and fetches the current Order by resource.id.
  • Replaying a delivery uses the same provider identifier and does not create another transaction. Transient failures request redelivery.
  • Configured commit, void, refund, and Order Edit events produce the expected provider operation.
  • Running the deployment scripts more than once leaves one API Extension and one Subscription registered at the expected endpoints.

Verify and operate the integration

Verify the complete flow in a non-production Project and a suitable tax-service test environment.

Two correct behaviors commonly look like integration failures. A destination outside your configured nexus returns zero tax because you have no obligation to collect there. Some tax-service sandboxes also accept a recording call and return success without persisting the transaction. The sandbox dashboard then stays empty however correct your integration is. Verify the recording contract against the sandbox, but verify visibility against a live account.

  1. Verify configuration: confirm that deployment scripts registered the expected API Extension and Subscription. Confirm that secured values are not exposed in standard configuration or logs.
  2. Verify calculation: create a Cart in ExternalAmount mode with a priced Line Item, a Custom Line Item when supported, and a shipping selection. Use the expected origin and an address within configured nexus. Confirm that taxedPrice is present and that Line Items, Custom Line Items, shipping, and the Cart total match the tax-service quote. An absent taxedPrice means the integration did not apply setCartTotalTax. Check registration and trigger conditions, fail-open responses, tax-service failures, and the returned actions. It does not mean that the tax service returned zero.
  3. Verify product and Customer mapping: repeat the calculation with different product tax codes and with exempt and non-exempt Customers. Confirm that the quote request contains the expected classification and exemption identifiers.
  4. Verify call reduction: repeat an update that does not change a tax input and confirm that it does not create another tax-service quote. Then change an address, quantity, discount, shipping selection, or classification and confirm that tax is recalculated.
  5. Verify recording: create an Order and confirm that the synchronizer records or commits exactly one provider transaction with the expected totals and stable Order identifier. Confirm the transaction against a live account, then remove the test transactions so that they do not reach reporting or filing.
  6. Verify redelivery: replay the same Order Message and confirm that the provider transaction is unchanged and no duplicate is created.
  7. Verify reverse lifecycle events: execute the configured cancellation and return flows. Confirm that the provider transaction is voided or refunded once, including the correct amount for a partial refund.
  8. Verify the failure policy: make the tax service unavailable. Confirm that calculation blocks or degrades as designed, that Order creation cannot bypass the approved policy, and that asynchronous recording recovers through redelivery or replay.
  9. Verify reconciliation: run the reconciliation process and confirm that it reports no difference after the normal flow, then detects and repairs a deliberately omitted recording.

Diagnose common failures

Use these signals to separate an integration fault from expected behavior.

SymptomLikely causeResolution
taxedPrice is absent from the Cart.The integration did not apply setCartTotalTax. The Extension can be unregistered, the trigger can be unmatched, or a fail-open or incomplete response can contain no tax actions.Confirm that the deployment script registered the Extension. Check the trigger condition, tax-service call, failure-policy path, and returned actions.
Tax is zero at a real destination.The destination is outside the configured nexus.Check the nexus settings in the tax-service account. Zero is correct outside nexus.
Order creation fails, reporting a missing external tax amount and rate.A priced element was not taxed.Emit the matching tax amount action. Check Custom Line Items and shipping first.
The Cart operation fails although the calculator responded.The response was not 200 or 201, or the Extension exceeded its timeout.Return 200 or 201. Set the tax-service timeout below the Extension timeout.
An InvalidOperation error is returned when setting a shipping tax amount.shippingKey is missing for Multiple ShippingMode, or supplied for Single.Set shippingKey only for a Cart with Multiple ShippingMode.
Cart tax does not match the tax-service quote.The Cart uses External mode, or another Extension changed a tax input afterwards.Use ExternalAmount. Declare Extension chaining dependencies on Extensions that change tax inputs.
No transaction appears in the tax-service dashboard.Recording did not run, or the sandbox does not persist transactions.Confirm that the synchronizer processed the Message. Verify visibility on a live account.
The tax service holds duplicate transactions.Recording is not idempotent across redelivery.Key the transaction on a stable Order-derived identifier. Treat a duplicate as success.

Monitor calculation latency, tax-service errors, quote skips, quote executions, Subscription health, redeliveries, and reconciliation differences. Include the API Extension correlation ID and Order ID in structured logs. These fields let you trace a Cart-to-Order flow across both applications.