Quote live shipping rates from a carrier or rate service on the Cart, and write labels and tracking numbers back to the Order.
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, andjobapplications. - No application template models shipping. Review the Application templates overview to confirm whether a suitable starting point exists.
shippingis 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.
| Requirement | Where it belongs |
|---|---|
| Zones, Shipping Method modeling, tiered rates, predicates, and free-shipping thresholds, with no external service in the loop | Shipping Methods and Shipping and Delivery overview |
| Live rates, labels, or tracking from a carrier or rate service | This guide |
| Order orchestration, allocation, fulfillment status, and the Delivery, Parcel, and tracking write-back owned by an order management system | Integrate an order management system |
| Tax on the shipping line | Integrate external tax |
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
| Requirement | Built-in mechanism | Integration needed |
|---|---|---|
| Flat rate per country or region, free above a threshold | Zones and zone rates with freeAbove | No |
| Price by weight, volume, item count, or a previously calculated distance band | Tiered rates over Cart Score, optionally with a PriceFunction | No, after application logic calculates the score |
| Price by an abstract bucket such as light or bulky | Tiered rates over Cart Classification | No |
| Option restricted by Store, address, warehouse, or Cart contents | Shipping Method predicates | No |
| Same-day delivery or click-and-collect as a distinct option | A Shipping Method with a predicate | No |
| One exact price known only from a third party, late in checkout | Cart freeze and Set CustomShippingMethod | Not necessarily |
| Live multi-carrier rate shopping, negotiated account rates, live service levels and delivery estimates | None | Yes |
| Labels, pickup-point selection, tracking numbers, or return labels from a carrier API | None | Yes |
Check the two constraints that close the built-in route
Two Project-level constraints decide borderline cases.
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.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.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.
| Flow | What it does | Application type |
|---|---|---|
| Rate quoting | Calls the carrier during checkout and puts a price on the Cart | service, registered as a Cart API Extension |
| Label and tracking | Books a shipment after the Order exists and records the tracking number | event, driven by a Subscription |
| Reconciliation or status polling | Retrieves tracking status where the carrier has no outbound webhook | job, with a 30-minute request timeout |
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?
Multiplemode supports split shipments with per-Line Item methods and addresses, and it changes every update action the integration emits. No update action changesshippingModeafter 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
isReturnset totrue - 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
view_connectors scope to call the Search Connectors endpoint:GET https://connect.{region}.commercetools.com/connectors/search?integrationTypes=shipping&private=false
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.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.
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
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
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.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.
| Template | What maps across | What you must supply |
|---|---|---|
| Tax integration template | A Cart API Extension that calls an external service and returns update actions, plus an Order Subscription | Carrier request mapping, rate selection, fallback behavior, and lifecycle scripts that register the required resources |
fulfilment-integration template, available through commercetools connect init | An Order export application and an inbound updates application, which is the shape of the label and tracking loop | Carrier booking, idempotency, the Parcel and tracking actions, and lifecycle scripts that register the Subscription |
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
| Characteristic | Tiered rate over score | Custom Shipping Method | Late one-off quote |
|---|---|---|---|
| Update action | Set ShippingRateInput | Set CustomShippingMethod or Add CustomShippingMethod | Set CustomShippingMethod after a Cart freeze |
| Who computes the price | commercetools, from the tier table | Your integration, verbatim from the carrier | Your integration, verbatim |
| Appears in matching Shipping Methods | Yes, with the matching tier flagged isMatching | No | No |
| Several carrier options side by side | Yes, one Shipping Method per option | Only through an endpoint you build | No, one price |
| Arbitrary carrier amounts | No, the amount must fit a tier or a PriceFunction | Yes | Yes |
| Cost | Uses the single shippingRateInputType of the Project and needs tier tables | The storefront must source the option list from you | Needs a freeze step in the checkout flow |
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.
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
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.Disabled, the Cart has no taxes, so the custom Shipping Method needs no tax field.Quote once late in checkout
SoftFreeze FreezeStrategy, which is the default. HardFreeze also freezes Shipping Methods, and Set CustomShippingMethod is not allowed on a Cart frozen that way.Build the rate application
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
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.
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.
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.
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
service application that returns the quoted options, each with its carrier, service level, price, and estimated delivery window.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.
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.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
event application, which receives Messages through a Subscription and a Connect-provisioned message broker.Trigger on the Message that means ready to ship
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.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.Make the write-back idempotent
deliveryKey, which Add Delivery describes only as the key of an existing Delivery.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.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
Configure and deploy the Connector
Grant only the permissions the enabled applications use
Declare scopes and let Connect provision the API Client rather than supplying credentials by hand.
| Scope | Needed by | Purpose |
|---|---|---|
manage_extensions | Rate application lifecycle | Register and remove the Cart API Extension |
manage_shipping_methods | Rate application lifecycle | Provision the fallback Shipping Method |
view_tax_categories | Rate application | Resolve the Tax Category a custom Shipping Method carries |
manage_subscriptions | Label application lifecycle | Register and remove the Order Subscription |
manage_orders | Label application | Read the Order and write the Delivery, Parcel, and tracking actions |
manage_types | Both lifecycles | Create the Custom Types for the quote hash and the carrier shipment identifier |
manage_key_value_documents | Rate application, optional | Hold the quote cache in Custom Objects |
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.
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:
- 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.
- 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.
- 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. - Verify the price on the Cart. Apply the chosen option and read
shippingInfoon the Cart. The amount must match the carrier quote exactly, and the tax on the shipping line must be present and correct. - Confirm the rate survives to the Order. Create the Order and check that
shippingInfo.priceequals the quoted amount. A rate that changes between quote and Order is a cache lifetime or re-quote defect. - 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.
- 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.
- 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.
- 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
| Symptom | Likely cause | Resolution |
|---|---|---|
| The application logs a successful quote, but the storefront shows no shipping options | The rate landed as a custom Shipping Method, which is not a Shipping Method and never appears in matching Shipping Methods | Move 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 logs | The Cart API Extension was never registered, or its conditional trigger does not match the Cart | Query 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 added | The trigger predicate references an optional field without the is defined operator, so it cannot be evaluated | Guard every optional field in the predicate with is defined |
| Two labels and two Deliveries for one Order, with the handler apparently succeeding | The handler exceeded the 10-second event acknowledgment budget, so the Message was redelivered | Acknowledge as soon as the work is durably recorded, or move the carrier call off the handler |
| The Extension cannot be registered at all | The Project holds the maximum number of API Extensions | Consolidate Extensions, or use a tiered rate, which needs no per-Cart carrier call |
| Cart updates become slow, then start failing | The carrier call is on the hot path with no short-circuit, and the Extension exceeds its budget | Add 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 outage | The integration is fail-closed | Apply the fallback Shipping Method provisioned by the deployment script, if fail-open is the agreed policy |
| Tier prices never change with weight | The shippingRateInputType of the Project is unset, or is not the type the tiers use | Set shippingRateInputType to the type the Shipping Method tiers are defined for |
| A shipping predicate that should match does not | Cart Score is not addressable in a predicate | Mirror 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 value | A tier score is an integer whose range starts at 0 | Scale the value and scale the tiers to match |
| The rate stops updating after the Cart is frozen | The Cart was frozen with HardFreeze, which also freezes Shipping Methods, so Set CustomShippingMethod is rejected | Freeze with SoftFreeze |
| The shipping line is untaxed, or the Order fails to be created | A custom Shipping Method was supplied without taxCategory under Platform TaxMode, without externalTaxRate under External, or without complete tax data under ExternalAmount | Supply 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 another | The origin address, unit system, or dimensional-weight divisor is misconfigured for that Region | Check the ship-from configuration and the carrier's per-Region conventions |
| Rates are plausible but consistently higher than expected | The sandbox and production carrier accounts use different pricing configurations | Reverify against a production carrier account |
| Two labels and two Deliveries for one Order | A redelivered Message was processed with no durable idempotency record | Recompute 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 Order | The Subscription was never registered, the subscribed Message type is not the one emitted, or an order management system owns the write-back | Query 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 ordered | The API does not validate delivered quantities against ordered quantities | Sum the quantities on existing Deliveries and compare them against the Line Items before writing |