Integrate a shipping carrier or rate service

Ask about this Page
Copy for LLM
View as Markdown

Quote live shipping rates from a carrier or rate service on the Cart, and write labels and tracking numbers back to the Order.

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

A shipping integration connects commercetools to a carrier, a multi-carrier rate-shopping engine, or a label and shipping-execution platform. This guide covers two flows: a synchronous rate quote that puts a carrier's price on the Cart during checkout, and an asynchronous write-back that books a shipment and records its tracking number on the Order.

Three facts shape the work before you plan any of it, and each is the opposite of what a payment integration would lead you to expect.

  • Checkout defines no shipping Connector contract. Checkout defines Connector contracts for two categories only, payment Connectors and gift card Connectors. A shipping integration has no enabler, no processor, and no session handshake to conform to. It is a plain Connect Connector built from service, event, and job applications.
  • No application template models shipping. Review the Application templates overview to confirm whether a suitable starting point exists.
  • shipping is a valid IntegrationType. The IntegrationType classifies a Connector in the registry and is how you search for one, but it prescribes nothing about the shape of the Connector.

Some shipping vendors provide a hosted integration rather than an installable Connector. Confirm the deployment model before deciding whether to configure an existing integration or build a Connector.

This guide does not cover order orchestration or fulfillment. Draw that boundary before you start.

RequirementWhere it belongs
Zones, Shipping Method modeling, tiered rates, predicates, and free-shipping thresholds, with no external service in the loopShipping Methods and Shipping and Delivery overview
Live rates, labels, or tracking from a carrier or rate serviceThis guide
Order orchestration, allocation, fulfillment status, and the Delivery, Parcel, and tracking write-back owned by an order management systemIntegrate an order management system
Tax on the shipping lineIntegrate external tax
The highest-risk overlap is labels and tracking. When an order management system or warehouse management system is in the picture, it books the carrier and holds the tracking number, and the write-back belongs to that integration. Two systems writing the same Delivery and Parcel data is a data-integrity defect rather than redundancy. Build the write-back described in this guide only when commercetools calls the carrier directly.
Before you start, you need a commercetools Project, an account and API credentials with the carrier or rate service, and an answer to the built-in capability question in the next section. For the wider planning framework, see Integration planning and patterns.

Rule out built-in Shipping Methods first

Most requests for a shipping integration are Shipping Method modeling problems. commercetools ships Zones, per-zone and per-currency rates, free-shipping thresholds, predicates, and tiered rates. Answer this question explicitly and record the answer, because a design that uses only those capabilities has no service to deploy, no latency budget, and no failure mode on the checkout path.

Match the requirement to a built-in mechanism

RequirementBuilt-in mechanismIntegration needed
Flat rate per country or region, free above a thresholdZones and zone rates with freeAboveNo
Price by weight, volume, item count, or a previously calculated distance bandTiered rates over Cart Score, optionally with a PriceFunctionNo, after application logic calculates the score
Price by an abstract bucket such as light or bulkyTiered rates over Cart ClassificationNo
Option restricted by Store, address, warehouse, or Cart contentsShipping Method predicatesNo
Same-day delivery or click-and-collect as a distinct optionA Shipping Method with a predicateNo
One exact price known only from a third party, late in checkoutCart freeze and Set CustomShippingMethodNot necessarily
Live multi-carrier rate shopping, negotiated account rates, live service levels and delivery estimatesNoneYes
Labels, pickup-point selection, tracking numbers, or return labels from a carrier APINoneYes
For the modeling itself, see Shipping Methods and Multiple Zones and currencies.

Check the two constraints that close the built-in route

Two Project-level constraints decide borderline cases.

The shippingRateInputType of the Project is a single choice across CartValueType, CartClassificationType, and CartScoreType, not a mix. If Cart Score already serves another purpose, that option is spent and the tiered-rate route is closed.
A Project holds up to 100 Shipping Methods. A design that creates one Shipping Method for every combination of carrier, service level, and Zone reaches that ceiling. A design that needs dozens of near-identical Shipping Methods is a signal that the rates want to be quoted rather than enumerated.
Two behaviors of Cart Score surprise implementers who choose the built-in route late. A CartScoreTier score is an integer whose range starts at 0, where the default price covers 0 and tiers start at 1, so a fractional value such as a distance in kilometers must be scaled before it is used and the tiers scaled with it. Cart Score is also not addressable in a Shipping Method predicate. When the same value must both select a tier and restrict availability, write it to a Cart Custom Field as well and reference that field in the predicate.

Define the shipping integration requirements

The integration path and the design both follow from these decisions. Record them before you evaluate a Connector or write code.

Decide what the integration owns

Two flows can be in scope independently, and this is the largest scope lever in the project.

FlowWhat it doesApplication type
Rate quotingCalls the carrier during checkout and puts a price on the Cartservice, registered as a Cart API Extension
Label and trackingBooks a shipment after the Order exists and records the tracking numberevent, driven by a Subscription
Reconciliation or status pollingRetrieves tracking status where the carrier has no outbound webhookjob, with a 30-minute request timeout
Settle the boundary with fulfillment before you scope the second flow. If an order management system books shipments, the label and tracking flow is out of scope for this integration entirely. For the ownership decision across all data domains, see Plan integrations.

Scope the flows

Answer and record the following questions, because each one changes the design:

  • Which service, and what does it do? A single carrier's API, a multi-carrier aggregator, a rate or checkout-rules engine, or a label and shipping-execution platform. These need different applications and different credentials.
  • Must quoted options appear in the shopper's shipping-options list? Get matching ShippingMethods for a Cart returns Shipping Methods only. If the storefront reads that endpoint and you land carrier prices as custom Shipping Methods, the quoted options never appear. This determines the landing mechanism, so get the answer now.
  • Single or Multiple ShippingMode? Multiple mode supports split shipments with per-Line Item methods and addresses, and it changes every update action the integration emits. No update action changes shippingMode after the Cart is created, so this is a requirements decision. For a worked example, see Multiple Shipping Addresses and Methods.
  • What granularity does a rate apply to? The whole Cart, a shipment group, or a Line Item. Are pickup points or parcel lockers in scope, which is address selection rather than pricing?
  • What happens when the carrier is slow or unavailable? Block the Cart, or fall back to a Shipping Method already defined in the Project. This is a business decision about pricing risk, not a technical default.
  • Which event means ready to ship? Order creation alone rarely does. Booking a label the moment an Order exists buys labels for unpaid Orders.
  • Which Region and Project? Connect hosts and API hosts are Region-specific.

Record special requirements

Capture anything the list above does not cover as its own requirement, because each one can move the decision in the next section from configuring a Connector toward building one:

  • dimensional weight, oversize surcharges, or hazardous-goods restrictions
  • duties, delivery-duty-paid pricing, or other cross-border charges
  • insurance, signature on delivery, or carrier cut-off times
  • delivery-date promises that the storefront displays
  • return labels, which are modeled as a Parcel whose tracking data has isReturn set to true
  • multi-origin or multi-warehouse routing, where the ship-from address varies per Cart
  • negotiated account rates that differ from a carrier's published rates

Choose an integration path

Work through these options in order and stop at the first one that meets your requirements. Each later option is more to build and more to maintain.

Check for an existing shipping Connector

Check the current Connector inventory rather than relying on a remembered list, because published Connectors and their versions change. Use an API Client with the view_connectors scope to call the Search Connectors endpoint:
Search for shipping Connectorshttp
GET https://connect.{region}.commercetools.com/connectors/search?integrationTypes=shipping&private=false
Set private to false so that the response covers all available Connectors rather than only those already assigned to your Project. Add integrationTypes=oms when fulfillment is also in scope, because a Connector that books shipments is commonly classified as order management. For the host to use in each Region, see Hosts and authorization. The same search is available in the Merchant Center.
Compare certification, source availability, installation requirements, and Region support using the canonical Connector representation. Record the Connector key and version you evaluated, or record that none exists.

Confirm that a candidate is Connect-deployable

An advertised commercetools integration can be a hosted service, an endpoint that your code calls, or a listing in the vendor marketplace. None of those options necessarily installs through Connect.

Confirm a Connect affordance before you call a candidate installable: a public Connector repository, a connect.yaml file, and a Connector entry you can deploy. A listing in the technology partner directory is not automatically an installable Connector. For installable Public Connectors, use Connect in the Merchant Center.

When the fit turns out to be a vendor-hosted integration, follow the vendor's onboarding rather than the Connect deployment model. The alternative is a Connect Connector that calls the same vendor API, which is the build described in the rest of this guide.

Prove that a gap is not configuration

When a Connector matches your carrier but appears to lack a behavior, confirm that configuration cannot close the gap before you consider forking it. Check whether the Connector exposes enabled carriers, service levels, markup, default package dimensions, and the origin address as configuration. Check whether Shipping Method predicates and the isDefault flag control which options appear and their order. Also check whether the carrier account controls cut-off times, insurance, and signature on delivery.

Fork or build

If a Connector matches your carrier, has a genuine gap that configuration cannot close, and its source is available, fork it. Before you commit, check which applications its connect.yaml declares, whether the rate path uses Set ShippingRateInput or a custom Shipping Method, whether the rate path has a timeout and a fallback, and whether the write-back is idempotent. The landing mechanism is the expensive thing to change after the fact.
If no Connector matches your carrier, build one. No template models shipping, so scaffold the applications your scope requires with the Connect CLI:
Scaffold the applications a shipping integration needsbash
commercetools connect init my-shipping-connector
commercetools connect application add --type service
commercetools connect application add --type event

Two existing templates are structurally close enough to provide starting points. Neither template implements a complete shipping integration.

TemplateWhat maps acrossWhat you must supply
Tax integration templateA Cart API Extension that calls an external service and returns update actions, plus an Order SubscriptionCarrier request mapping, rate selection, fallback behavior, and lifecycle scripts that register the required resources
fulfilment-integration template, available through commercetools connect initAn Order export application and an inbound updates application, which is the shape of the label and tracking loopCarrier booking, idempotency, the Parcel and tracking actions, and lifecycle scripts that register the Subscription
The fulfilment-integration template is offered by the CLI but is not listed on the Application templates overview.

Whichever option you choose, the design in the following sections is the same. Only the question of who implements it changes.

Decide how a quoted rate lands on the Cart

A carrier returns a number. Three mechanisms turn that number into the shipping price on a Cart, and they differ in what the storefront can see. This decision is expensive to reverse, because it determines which endpoint the checkout UI calls.

Compare the three landing mechanisms

CharacteristicTiered rate over scoreCustom Shipping MethodLate one-off quote
Update actionSet ShippingRateInputSet CustomShippingMethod or Add CustomShippingMethodSet CustomShippingMethod after a Cart freeze
Who computes the pricecommercetools, from the tier tableYour integration, verbatim from the carrierYour integration, verbatim
Appears in matching Shipping MethodsYes, with the matching tier flagged isMatchingNoNo
Several carrier options side by sideYes, one Shipping Method per optionOnly through an endpoint you buildNo, one price
Arbitrary carrier amountsNo, the amount must fit a tier or a PriceFunctionYesYes
CostUses the single shippingRateInputType of the Project and needs tier tablesThe storefront must source the option list from youNeeds a freeze step in the checkout flow
Prefer the tiered rate when the price is a function of one value you can reduce to an integer, such as weight, dimensional weight, a distance band, or a Zone index. The storefront keeps calling Get matching ShippingMethods for a Cart unchanged and your integration supplies only the score.
Use a custom Shipping Method when the carrier's amount is genuinely arbitrary, which covers negotiated account rates, fuel and handling surcharges, and live service-level pricing. The consequence is architectural rather than cosmetic. A custom Shipping Method is not a ShippingMethod, so matching Shipping Methods never lists your quoted options. The storefront must fetch the option list from an endpoint your integration exposes and then apply the option the shopper chooses. Plan that endpoint at design time, because discovering the need for it later means reworking the checkout UI.

Combining both mechanisms in one integration is legitimate, such as tiered rates for standard delivery and a quoted rate for an express option. Say so explicitly in your design, because the storefront then merges two sources of options.

Two details of the update actions differ between shipping modes. Set CustomShippingMethod requires the Cart to have Single ShippingMode and a shippingAddress. The action accepts estimatedDelivery for the delivery window from the carrier. Add CustomShippingMethod is the Multiple mode equivalent. It requires shippingKey and shippingAddress and has no estimatedDelivery field. In Multiple mode, use Set EstimatedDelivery with the shippingKey of each Shipping Method. Omitting estimatedDelivery from Set CustomShippingMethod clears any estimate already on the Cart.

Supply tax on the shipping line

A custom Shipping Method carries no Tax Category of its own, and the field you must supply depends on the TaxMode of the Cart. Supply taxCategory under Platform, and externalTaxRate under External. Under External a Cart cannot be ordered until the Shipping Method has an external Tax Rate, so an omission surfaces as a failure to create the Order rather than as an error on the Cart.
ExternalAmount needs more care. Landing a shipping rate is a price-affecting update, which removes taxedPrice and taxRate. Apply the rate first. Then set the tax amounts for every Line Item, Custom Line Item, and Shipping Method. When the rate update removes the external tax amount for the Shipping Method, issue Set ShippingMethod TaxAmount in a separate request. Finally, use Set Cart Total Tax to restore taxedPrice. Extension chaining cannot satisfy the separate-request requirement because chained Extensions return their actions in the same operation.
Under Disabled, the Cart has no taxes, so the custom Shipping Method needs no tax field.
When an external tax integration also extends the Cart, the shipping rate must land before tax is calculated. See Sequence the rate against other Extensions and coordinate with Integrate external tax.

Quote once late in checkout

When you need one exact price and no option list, you can quote outside the Cart lifecycle instead of registering an API Extension. Create the Cart with a zero-amount Shipping Method, set the shipping address, freeze the Cart, request the quote, and apply it with Set CustomShippingMethod before creating the Order.
Freeze with the SoftFreeze FreezeStrategy, which is the default. HardFreeze also freezes Shipping Methods, and Set CustomShippingMethod is not allowed on a Cart frozen that way.
An alternative applies the rate after the Order exists, using an Order Edit that stages the same action. Choose it when the exact amount is unavailable until after Order creation, and accept that the Order total changes after the shopper has seen it.

Build the rate application

The rate application is a service application registered as a Cart API Extension. commercetools calls it before it persists a Cart change, so its latency and availability become part of every matching Cart operation.

The application never writes the Cart itself. It returns update actions, and commercetools applies them inside the same operation.

Size the Extension budget before the carrier call

Size the carrier timeout within the API Extension time limits, leaving headroom for mapping and response handling. The commercetools API must establish a connection within 1 second, and the Extension must return a result within 2 seconds by default. You can configure a timeout per Extension up to a maximum of 10 seconds, and anything longer requires a per-Project increase that is subject to a performance review. Carrier rate APIs, and multi-carrier rate shopping in particular, can exceed that budget.
The platform does not retry an API Extension within an API call, although later API calls reach it again. A missing or invalid response therefore fails the entire Cart update with ExtensionNoResponse, ExtensionBadResponse, or ExtensionUpdateActionsFailed when the actions you return are not valid for the shipping mode, address, or freeze state of the Cart. Every carrier timeout becomes a shopper who cannot change their Cart.

Set the outbound carrier timeout below the configured Extension timeout, with headroom for your own mapping work, and make both configuration values rather than constants. When you rate-shop across carriers, issue the requests in parallel and return the carriers that answered rather than waiting for the slowest. Measure the carrier's actual latency and size the timeout from that measurement.

Return a successful API Extension response with no more than 100 update actions. This limit matters for Multiple ShippingMode when one quote produces actions for several Shipping Methods.

Avoid the carrier call when nothing rate-relevant changed

Most Cart updates in a checkout flow cannot change a shipping price. Adding a note, setting an email address, or applying a discount code leaves the origin, destination, weight, and contents unchanged.

Hash the inputs that do move the price: the shipping address, the Line Item SKUs, their quantities, the package weight, the package dimensions, and the selected service level. Store the hash and the resulting quote in a Cart Custom Field or a Custom Object, and return no update actions when the hash matches. Give the cached quote a time to live short enough that a stale rate cannot reach an Order.

The short-circuit matters more than it first appears. When the shopper selects an option, the storefront applies it with a fresh Cart update, which triggers the Extension again. Without the hash comparison, every selection costs a second carrier call inside the same budget.

Reduce calls before the application runs by adding a conditional trigger so that the Extension fires only for Carts that can be rated, such as those with a shipping address and priced items. Follow the conditional-trigger rules for optional fields so that the predicate can always be evaluated.

Decide fail-open or fail-closed

A carrier outage must not make Carts unusable, but a fail-open integration can price a Cart below cost. This is a business decision about pricing risk. Take it explicitly, record it, and state it in the Connector documentation.

If the business chooses to fail open, provision a fallback Shipping Method whose predicate always matches and whose rates cover every currency in scope. Apply it when the carrier does not answer, and log the degradation with the correlation identifier. Scope the fallback globally or to every Store in scope. A Cart that belongs to a Store matches only globally scoped Shipping Methods and those scoped to that Store. Choose fail-closed behavior when an incorrect shipping price is worse than a blocked checkout.

Do not use an Extension error to signal that no shipping is available. Model unavailability as an empty option list or a non-matching predicate, and reserve errors for genuine failures.

Serve the option list when rates are not Shipping Methods

When quoted rates land as custom Shipping Methods, matching Shipping Methods cannot list them, so the storefront needs another source. Add a second HTTP route to the same service application that returns the quoted options, each with its carrier, service level, price, and estimated delivery window.
That route is not an API Extension. The Connect service timeout applies rather than the Extension budget, and you authenticate the caller yourself. Have the route and the Extension read the same cached quote, so the list the shopper sees and the price that lands on the Cart cannot disagree.

Sequence the rate against other Extensions

API Extensions for the same resource and action run concurrently by default. A tax Extension can therefore calculate against a Cart state that does not yet carry the shipping rate and record tax that does not match the persisted result.

Declare a dependency from the tax Extension to the rate Extension so that the tax Extension waits for the rate to land and receives the resulting Cart state. See Extension Chaining for the dependency model, and register the dependency in the deployment scripts alongside the Extension itself.
A chain is bounded. An Extension declares at most 5 direct dependencies, a chain runs at most 3 levels deep, and the cumulative execution time of the chain must stay within the 60-second limit the platform enforces for an API request. Two chained Extensions that each call an external service consume a large share of that budget. A Project also holds up to 25 API Extensions, so confirm there is room before you plan a per-Cart carrier call.
Chaining orders the Extensions, but it cannot split them across requests. When the Project uses the ExternalAmount TaxMode, the shipping tax amount still needs the separate request described in Supply tax on the shipping line.

Test the decisions, not the carrier

Mock the carrier boundary and assert on what your code decided rather than on what the carrier returned. The suite must run without a deployment and without secrets.

Cover the quote selection when several carriers answer, the update actions emitted for each landing mechanism, the short-circuit returning no actions for a Cart whose rate-relevant inputs are unchanged, the fallback path when the carrier exceeds its timeout, and the tax field supplied for the TaxMode of the Cart.

Write labels and tracking back to the Order

Build this flow only when commercetools calls the carrier directly. When an order management system or warehouse management system books shipments, that integration owns the Delivery and Parcel write-back, and this application must not exist. See Integrate an order management system for that flow.
Use an event application, which receives Messages through a Subscription and a Connect-provisioned message broker.

Trigger on the Message that means ready to ship

Order creation alone rarely means ready to ship. For Orders created from Carts, subscribe to OrderCreated and combine it with a state gate such as OrderStateChanged. If recurring or imported Orders are in scope, also handle OrderCreatedFromRecurringOrder or OrderImported. Acknowledge Messages that do not meet the business gate without acting on them.
Retrieve the Order by resource.id rather than reading the Message payload. Subscriptions deliver at least once with no ordering guarantee, so a payload can describe an older state than the Order now has. That page also documents resource.id with sequenceNumber as the pair an idempotent processor uses to discard a duplicate and restore ordering.
Follow the Connect event acknowledgment behavior. A carrier booking followed by Order writes might not fit inside the acknowledgment budget. Either acknowledge as soon as the work is durably recorded, or move the carrier call to a job application and keep the handler work short. A late acknowledgment causes redelivery, which can produce duplicate labels without the idempotency controls in the next section.
Map each processing outcome to a response status deliberately. Integrate an order management system gives the full outcome-to-status mapping, and it applies here unchanged.

Make the write-back idempotent

Expect redelivery, and treat each redelivery as a chance to buy a second label. Connect autoscales an application across instances, so an in-process record of a booking is lost on restart and is not shared between instances. A Subscription also retries a failed delivery for far longer than a Connect queue retains an unacknowledged Message, and the retry window varies by error type and Project type. Size the recovery plan against the Subscription delivery guarantees, not only the Connect queue retention period.
Before calling the carrier, create a durable booking record in a Custom Object or an external store. Derive its key from stable inputs such as the Order identifier and shipment index. If the record already exists, acknowledge without booking again. When the carrier supports idempotency keys, send the same key with the booking request. After the carrier responds, store the carrier shipment identifier and the resulting Delivery identifier in the record. This closes the retry window in a store you control rather than relying on deliveryKey, which Add Delivery describes only as the key of an existing Delivery.
Add Delivery requires existing shipping data on the Order. It fails unless the Order has shippingInfo for Single ShippingMode, or shipping for Multiple ShippingMode. In Multiple mode the action also requires shippingKey, which is the key of the Shipping Method the Delivery belongs to.
Write the shipped Line Items and quantities in the items field of Add Delivery. Include parcels in the same action when their measurements and tracking numbers are known at booking time. Otherwise, use Add Parcel to Delivery and Set Parcel Tracking Data for the TrackingData. Writing known items, parcels, and tracking data in one action removes the partial-write window between them. Set isReturn to true in the tracking data of a return label. Store the carrier shipment identifier in a Custom Field on the Delivery so that a later status update or cancellation can find it. When you issue several actions, send each against the version returned by the previous write.

Guard against overshipment

Apply the multiple-delivery quantity rule before adding a Delivery. Sum the quantities already present across existing Deliveries and compare them against the Line Item quantities. The integrating system must prevent overshipment.

Configure and deploy the Connector

Configure the Connector from the approved requirements, and use deployment scripts to register and remove every platform resource the applications depend on.

Grant only the permissions the enabled applications use

Declare scopes and let Connect provision the API Client rather than supplying credentials by hand.

ScopeNeeded byPurpose
manage_extensionsRate application lifecycleRegister and remove the Cart API Extension
manage_shipping_methodsRate application lifecycleProvision the fallback Shipping Method
view_tax_categoriesRate applicationResolve the Tax Category a custom Shipping Method carries
manage_subscriptionsLabel application lifecycleRegister and remove the Order Subscription
manage_ordersLabel applicationRead the Order and write the Delivery, Parcel, and tracking actions
manage_typesBoth lifecyclesCreate the Custom Types for the quote hash and the carrier shipment identifier
manage_key_value_documentsRate application, optionalHold the quote cache in Custom Objects
The rate application needs no Cart write scope, because it returns update actions and commercetools applies them. The manage_extensions and manage_subscriptions scopes cover reading as well as writing, so no separate view scope is required. For the full list, see Scopes. Requesting a scope the API Client was not granted returns a 400 invalid_scope response.

Separate configuration from secrets

Carrier API keys and account identifiers belong in secured configuration. Everything an operator changes without a code change belongs in standard configuration: the enabled carriers, the enabled service levels, the origin address, the default package dimensions, the dimensional-weight divisor, the markup, the carrier timeout, the quote cache lifetime, the fallback Shipping Method key, the Order state that gates label creation, and whether the Connector calls the carrier's sandbox or its live account.

Make the landing mechanism an explicit configuration value rather than an implicit consequence of the code. It is the decision most likely to be revisited, and it is the one an operator most needs to be able to read.

Register resources idempotently and verify the registration

The deployment script has more to do here than in most integrations, because the applications depend on platform resources existing before the first shopper arrives:

  • the fallback Shipping Method, created only when absent, with a predicate that always matches and rates in every currency in scope
  • the Custom Types for the Cart quote hash and the Delivery shipment identifier
  • the Cart API Extension, registered at the rate application's endpoint with an authenticated destination
  • the Order Subscription, registered at the label application's endpoint

Write each registration as a read followed by a create or update so that a redeployment does not fail on a resource that already exists, and have the undeployment script remove what the deployment script created.

Verify the registration rather than inferring it from a successful deployment. Query the Extensions and Subscriptions in the Project after deploying and confirm that the destinations and triggers are the ones you expect. A deployment script that only installs dependencies registers no platform resources, so no quote or Message delivery occurs.

Validate carrier credentials at deploy time

Make one inexpensive carrier call, such as an account lookup or a rate ping, in the deployment script. Incorrect credentials then fail the deployment instead of failing the first shopper's Cart.

Verify the round trip

Verify against a Project with a real carrier account. A shipping integration that passes its unit tests can still fail in three ways that only appear against a running Project: the shopper cannot see the option, the shopper cannot check out at all, or the price on the Order is not the price the carrier will invoice.

Deploy to the production DeploymentType before you draw conclusions about latency. Sandbox cold starts can exceed the API Extension timeout and should not be treated as carrier latency.

Run these in order, because each one fails differently:

  1. Confirm the Extension is registered. Query the Extensions in the Project and check that yours is present with the destination and trigger you expect. A deployment script that did not run is the most common cause of nothing happening at all.
  2. Trigger a quote. Create a Cart, set a shipping address, and confirm from the application logs that exactly one carrier call was made, with the origin, destination, and weight you expect.
  3. Check that the option is visible to the shopper. For a tiered rate, confirm that matching Shipping Methods returns the method with the correct tier flagged isMatching. For a custom Shipping Method, confirm that your option-list endpoint returns the quoted options. Verify whichever endpoint the storefront actually calls, because this is the failure unit tests never catch.
  4. Verify the price on the Cart. Apply the chosen option and read shippingInfo on the Cart. The amount must match the carrier quote exactly, and the tax on the shipping line must be present and correct.
  5. Confirm the rate survives to the Order. Create the Order and check that shippingInfo.price equals the quoted amount. A rate that changes between quote and Order is a cache lifetime or re-quote defect.
  6. Update the Cart in a way that cannot change the price. Change a Custom Field or set an email address and confirm that no carrier call was made. The short-circuit is what keeps the Extension inside its budget in production.
  7. Book one label. Trigger the Order Message, confirm a shipment exists at the carrier, and check that the durable booking record points to one Delivery containing the expected items.
  8. Check that tracking reaches the Parcel. Confirm that the Parcel on the Order carries the carrier tracking identifier and carrier name, and that the number resolves on the carrier tracking page.
  9. Redeliver the same Message. Confirm that no second label and no second Delivery are created, and that the handler acknowledges.

Exercise the failure paths deliberately

These paths only prove themselves when you break something on purpose.

  • Carrier unreachable. Point the integration at an unreachable host. The Cart update must still succeed and the agreed fallback must apply. If Carts fail, the integration is fail-closed, so confirm that is what was chosen.
  • Carrier slow. Inject latency above the configured carrier timeout. The Extension must return on the fallback path within its own budget rather than riding the carrier's timeout into an ExtensionNoResponse.
  • Incorrect credentials. Deploy with a wrong API key. The deployment must fail rather than deferring the failure to the first shopper.
  • An address the carrier rejects. Use an undeliverable postal code or an address the service level does not cover. The shopper must see no available option rather than an error that blocks the Cart.

Confirm the rates against a production carrier account before go-live. Sandbox and production carrier accounts can use different pricing configurations.

Troubleshoot observable symptoms

SymptomLikely causeResolution
The application logs a successful quote, but the storefront shows no shipping optionsThe rate landed as a custom Shipping Method, which is not a Shipping Method and never appears in matching Shipping MethodsMove to a tiered rate, or serve the option list from the integration and have the storefront read it
No quote, no error, and nothing in the logsThe Cart API Extension was never registered, or its conditional trigger does not match the CartQuery the Extensions in the Project, and check the trigger against a Cart that should be rated
Every Cart update fails as soon as a conditional trigger is addedThe trigger predicate references an optional field without the is defined operator, so it cannot be evaluatedGuard every optional field in the predicate with is defined
Two labels and two Deliveries for one Order, with the handler apparently succeedingThe handler exceeded the 10-second event acknowledgment budget, so the Message was redeliveredAcknowledge as soon as the work is durably recorded, or move the carrier call off the handler
The Extension cannot be registered at allThe Project holds the maximum number of API ExtensionsConsolidate Extensions, or use a tiered rate, which needs no per-Cart carrier call
Cart updates become slow, then start failingThe carrier call is on the hot path with no short-circuit, and the Extension exceeds its budgetAdd the input hash and cached quote, set the carrier timeout below the Extension timeout, and rate-shop in parallel
No Cart can be updated during a carrier outageThe integration is fail-closedApply the fallback Shipping Method provisioned by the deployment script, if fail-open is the agreed policy
Tier prices never change with weightThe shippingRateInputType of the Project is unset, or is not the type the tiers useSet shippingRateInputType to the type the Shipping Method tiers are defined for
A shipping predicate that should match does notCart Score is not addressable in a predicateMirror the score to a Cart Custom Field and reference that field in the predicate
The score is rejected, or the price is wrong for a fractional valueA tier score is an integer whose range starts at 0Scale the value and scale the tiers to match
The rate stops updating after the Cart is frozenThe Cart was frozen with HardFreeze, which also freezes Shipping Methods, so Set CustomShippingMethod is rejectedFreeze with SoftFreeze
The shipping line is untaxed, or the Order fails to be createdA custom Shipping Method was supplied without taxCategory under Platform TaxMode, without externalTaxRate under External, or without complete tax data under ExternalAmountSupply the field required by the TaxMode, sequence the rate Extension before the tax Extension, set the shipping tax amount in a separate request under ExternalAmount, and finish with Set Cart Total Tax
Prices are correct in one country and wrong in anotherThe origin address, unit system, or dimensional-weight divisor is misconfigured for that RegionCheck the ship-from configuration and the carrier's per-Region conventions
Rates are plausible but consistently higher than expectedThe sandbox and production carrier accounts use different pricing configurationsReverify against a production carrier account
Two labels and two Deliveries for one OrderA redelivered Message was processed with no durable idempotency recordRecompute the booking key, check the durable record before booking, and send the key to carriers that support idempotent requests
The tracking number never reaches the OrderThe Subscription was never registered, the subscribed Message type is not the one emitted, or an order management system owns the write-backQuery the Subscriptions in the Project, check the Message type against the Order lifecycle, and confirm which system owns the Delivery
Deliveries report more items than were orderedThe API does not validate delivered quantities against ordered quantitiesSum the quantities on existing Deliveries and compare them against the Line Items before writing