commercetools

Overview

commercetools is an API-first, composable commerce platform, so there is no synchronous carrier callback at checkout the way Shopify or BigCommerce provide. Instead, the ShipRateAPI connector runs as a small service that responds to cart updates via a commercetools API Extension: it builds a payload from the cart and destination, POSTs it to the ShipRateAPI API server-side, and writes the returned rates back onto the cart as a JSON custom field for your storefront to render — all without the API key ever reaching the browser.

  • A commercetools Composable Commerce project
  • An API client with the manage_types and manage_extensions scopes (used by the registration script)
  • Node.js 18+ runtime — deploy on commercetools Connect or your own infrastructure
  • A publicly accessible HTTPS URL for the API Extension destination
Environment variables

Copy .env.example to .env and fill in the values:

SHIPRATE_API_ENDPOINT=https://api.shiprateapi.com/api/v1/quotes
SHIPRATE_API_KEY=sk_xxxxxxxxxxxxxxxxxxxx

CTP_PROJECT_KEY=your-project-key
CTP_CLIENT_ID=your_client_id
CTP_CLIENT_SECRET=your_client_secret
CTP_AUTH_URL=https://auth.europe-west1.gcp.commercetools.com
CTP_API_URL=https://api.europe-west1.gcp.commercetools.com

# A long random string. commercetools sends it back on every call as
# "Authorization: Bearer <secret>"; the connector rejects anything else.
#   openssl rand -hex 32
EXTENSION_SHARED_SECRET=your_long_random_shared_secret

APP_CALLBACK_URL=https://your-connector.example.com/extension
PORT=3012

# Optional — commercetools has no native weight or tag fields, so the
# connector reads them from these product-variant attributes.
WEIGHT_ATTRIBUTE=weight
TAGS_ATTRIBUTE=shiprate_tags
NAME_LOCALE=en
VariableDescription
SHIPRATE_API_ENDPOINTShipRateAPI quotes endpoint — https://api.shiprateapi.com/api/v1/quotes. Update the version path here without a code change. The connector sends X-Platform: commercetools automatically.
SHIPRATE_API_KEYYour ShipRateAPI API key for this store
CTP_PROJECT_KEYYour commercetools project key
CTP_CLIENT_ID / CTP_CLIENT_SECRETAPI client credentials from Settings → Developer settings → API clients
CTP_AUTH_URL / CTP_API_URLRegion-specific auth and API hosts for your project
EXTENSION_SHARED_SECRETRequired. A long random string used to authenticate inbound calls — see Request verification below. npm run register exits with an error if it is not set.
APP_CALLBACK_URLPublic HTTPS URL registered as the API Extension destination. This must point at the /extension route — commercetools POSTs the cart there on every Create and Update.
WEIGHT_ATTRIBUTEOptional (default weight). Product-variant attribute holding the item weight in kg. commercetools has no native weight field — without a matching attribute every item is priced at zero weight.
TAGS_ATTRIBUTEOptional (default shiprate_tags). Product-variant attribute holding comma-separated routing tags.
NAME_LOCALEOptional (default en). Locale used to resolve localised line-item names.
RATE_LABEL_FORMATOptional (default {Carrier} - {Service}). Template for each quote's label field. Tokens {Carrier} and {Service} (case-insensitive).
Installation & registration
npm install
node src/scripts/register.js   # creates the custom Type + API Extension
npm start

The register.js script provisions the two commercetools resources the connector relies on. Run it once per project, or again after the callback URL changes — re-running is safe, as existing resources are updated in place rather than duplicated.

ResourcePurpose
Custom Type ship-rate-apiApplied to carts, providing the shipRateApiQuotes String field the extension writes quotes into. Your storefront reads this field. If it already exists the script leaves it as-is.
API Extension ship-rate-api-quotesSubscribed to cart Create and Update, pointing at your APP_CALLBACK_URL and sending Authorization: Bearer <EXTENSION_SHARED_SECRET>.

Both names are fixed in src/lib/constants.js and must match between the registration script and the running service.

Request verification

Unlike Shopify and BigCommerce, commercetools does not HMAC-sign extension requests. Instead the extension is registered with an AuthorizationHeader destination, so commercetools sends a fixed header on every call:

Authorization: Bearer <EXTENSION_SHARED_SECRET>

The connector compares that header against its own EXTENSION_SHARED_SECRET using a constant-time comparison and rejects any mismatch with a 401. The same check guards both /extension and /rates. npm run register writes the secret into the extension destination for you, so the two sides always agree.

Never run without it. If EXTENSION_SHARED_SECRET is unset the connector logs a warning and skips verification entirely, leaving the endpoint open to anyone who finds the URL. Always set it in production.

How rates reach the cart

When a cart is created or updated, commercetools calls the API Extension. The connector resolves the destination and line items, requests rates from ShipRateAPI, and returns an update action that writes the quotes onto the cart as a JSON-encoded string in the shipRateApiQuotes custom field. If the cart already carries the ship-rate-api Type the connector patches that single field (setCustomField); otherwise it assigns the Type and seeds the field (setCustomType), so custom fields belonging to other Types are never clobbered. Carts with no shipping country or no line items are skipped.

The connector does not set a shipping method. It never emits a setShippingMethod action — it only publishes the available quotes. Parsing shipRateApiQuotes, presenting the options, and applying the buyer's choice (via setShippingMethodor a custom shipping method) is your storefront's responsibility.

Synchronous timeout. commercetools API Extensions must respond quickly. The timeout is enforced by commercetools, not the connector: register.js sets timeoutInMs: 2000 when it first creates the Extension. Note this is applied on creation only — it is not re-applied when an existing Extension is updated. The connector itself applies no timeout to the ShipRateAPI call. It does fail open, though: on any error it returns an empty UpdateRequest, leaving the cart unchanged rather than blocking checkout.

Endpoints
EndpointDescription
POST /extensionThe API Extension callback registered with commercetools. Returns an UpdateRequest writing quotes to the cart custom field.
POST /ratesA convenience endpoint for headless storefronts that would rather fetch quotes directly than read the cart custom field. Send a commercetools Cart object, or the same { resource: { obj } } envelope the extension receives, and get back { "rates": [...] }. It shares the same normalisation and ShipRateAPI client as the extension, so both paths return identical pricing, and it fails open with an empty array.
GET /healthUnauthenticated health check returning { "status": "ok", "service": "@nysa/commercetools-shiprate" }.

Both quote paths emit the same objects — the value of shipRateApiQuotes is this array, JSON-encoded as a string, so remember to JSON.parse() it:

{
  "carrierCode":     "dpd_uk",
  "carrierName":     "DPD UK",
  "methodCode":      "dpd_uk_express_delivery",
  "methodName":      "Express Delivery",
  "label":           "DPD UK - Express Delivery",
  "amount":          5.99,   // decimal, matches the cart currency
  "centAmount":      599,    // minor units — build a commercetools money object
  "currency":        "GBP",
  "minDeliveryDays": 1,
  "maxDeliveryDays": 2
}
Product tags

commercetools tags are driven by product attributes. Add a shiprate_tags attribute (type text) to the relevant product types, then set a comma-separated value on each product you want to route — e.g. fragile, cold-storage. The connector reads this attribute from each line item’s product and forwards the tags in the rate request. See Tags & Routing for the full list of recognised tags and their routing behaviour.